一维数组、数组越界问题(哨兵位)、左值与右值、参数传递

一、数组的定义
1、整型
(1)

int arr[5];//全未初始时,默认随机值
int arr[5] = {1,2};//部分未初始时,默认0
int arr[5] = {1,2,3,4,5};//数组内个元素依次对应赋值
int arr[] = {1,2};//此时数组长度为2(两个元素)

(2)聚合类型,不允许数组整体赋值,可以单元素进行赋值

int arr[5] = {1,2};
arr[3] = 3//此时数组第三个元素值为3

2、字符
字符数组的整体赋值只能在字符数组初始化时使用,不能用于字符数组的赋值,字符数组的赋值只能对其元素一一赋值
(1)字符数组:

char str[10]={ 'I',' ','a','m'};//部分未赋值时,默认'\0'
char str[10]={ 'I',' ','a','m',' ',‘h’,'a','p','p','y'};

(2)字符串:

  • 字符数组存储:
    数组长度为字符串实际长度加1(默认添加’\0’)
char str[ ]={"I am happy"};//三者均等价
char str[ ]={'I',' ','a','m',' ','h','a','p','p','y','/0'};     
char str[ ]="I am happy";
sizeof(str);//11
strlen(str);//10,计算实际字符串长度
char *p = "abcde";
printf("%s", p);//此时p相当于&arr
printf("%c", p[1]);//b
sizeof(p);//4
strlen(p);//5

C语言中字符数组和字符串定义和初始化的问题
(3)
0:整数0可以占,2字节(int)或4字节(long)
0.0:在计算机中,浮点数是没有绝对的0值的,所以0.0是指约等于0
‘\0’ :ASCII控制字符,是转义字符,代表空字符。作为字符串结尾字符。字符’\0’可以直接用ASCII码0表示,0不管是几进制,都是0(十进制),也就是0x00(十六进制)
‘0’ :ASCII码48,字符’0’只占1个字节.(char)
“0” :字符串常量

二、数组名的含义

作为数组名代表整个数组——>arr/&arr[0];
作为指针代表数组的首元素地址(数组首元素的首地址)——>&arr;

&arr[i]和arr+i含义相同,即arr+i是arr之后第i个元素地址

sizeof(arr);//整个数组所占内存长度
sizeof(&arr);//整个数组的地址的占用空间长度
arr+1;//移动一个元素后的长度//arr[1]
&arr+1;//跨过arr数组的下一个地址

1、计算数组长度(数组元素个数):
注意计算len时应与数组的定义放在一起,避免数组名退化成为指针(恒为4字节)

int len = sizeof(arr) / sizeof(arr[0]);//arr占用内存长度除以其首元素字节长度

2、与sizeof:仅检查当前括号内的类型大小,不进行语法检查

int arr[5];
&arr;//int (*)[4];也就是指向包含4个int数据的数组的指针。
sizeof(arr);//20//整个数组所占内存长度
sizeof(&arr);//4//整个数组的地址的占用空间长度
sizeof(arr[0]);//4
sizeof(arr[5]);//4//预编译,仅判断类型计算占用字节大小
sizeof(&arr[0]);//4
sizeof(&arr[5]);//4

int c = 1;
sizeof(c++);//1,且c不做自加运算仍为1

sizeof(函数名);//函数返回值类型大小

3、arr 与 &arr
arr,&arr 同值不同作用域
arr 数组首元素地址(小范围),arr + 1 指针指向当前类型变量的下一个
&arr 数组首地址(大范围),&arr + 1 跨过arr数组的下一个地址

三、数组越界问题
所有自动分配内存空间的数组均存放在栈内,栈的增长方式为:高地址—>低地址,VS中有8位的哨兵位(栈保护),当数组越界便会触碰哨兵位
特别的,数组元素在栈内的存放顺序为最后一个元素(高地址)—>首元素(低地址)
数组可分配的最大内存:与栈的大小有关

编译时期确定数组的大小;
运行时分配内存,初始化

四、数组与指针
这里写图片描述

//arr == &arr[0] 
int arr[5];
int *p = arr;//p是一个指向int类型的指针,p = arr,就是把数组的首地址赋给p,即 p 现在就是指向数组的首地址,通过 p 就可以访问整个数组,但是p这里只是是个指针变量,也就是 p 的本质没有改变,p不能和arr一样代表整个数组的内存,所以sizeof(p) == sizeof(int*)  != sizeof(arr)。
把数组的首地址赋给 p,但 p 的本质一个int类型的指针变量,所以也就可以对 p 进行 ++ 之类的运算。
p+1;//p->arr[1]

五、野指针
“野指针”不是NULL指针,是指向被释放的或者访问受限的垃圾内存的指针。
关于野指针
六、数组传参与传址
1、值传递:把值传递给函数作为参数。

void fun(int a,int b,int c)
{
a=456;b=567;c=678;
}
void main()
{
int x=10,y=20,z=30;
fun(x,y,z);
printf("%d,%d,%d\n",x,y,z);
}
最后的输出结果是102030

当调用函数时是以值的方式来传递的话,形参的值发生变化,而实参的值并没有跟着形参一起改变

2、地址传递:把地址传递给函数作为参数(指针、数组传递)。

void Swap(int *a,int *b); //用于交换两个变量值,需要地址,如果只传值,交换后原变量不会改变
{
      int temp = *p;
      *p = *q;
      *q = temp;
}
Swap(&a,&b);//调用

这里写图片描述
当调用函数时是以地址的方式来传递的话,形参的值发生变化,实参的值也跟着形参一起改变;要是想实现变量的真正交换,必须要用地址传递,而指针和数组名传递其实是一回事
3、对于数组:
在使用数组做参数时,编译器会自动将数组名转换为指向数组第一个元素的指针

void Fun(int *arr);
void Fun(int arr[]);
void Fun(int arr[5]);//三者意相同

代码改善:
(1)增加参数len, 限定元素个数

void Fun(int* arr, int len)
{
    for (int i = 0; i < len; ++i)
    {
        printf("%d\n",arr[i]) ;
    }
}

(2)传递数组的引用,避免被转换为指针,而且有了元素个数的信息,调用的时候,也必须传递一个含有3个元素的数组

void Fun(int (&arr)[3])
{
    for (int i = 0; i < 3; ++i)
    {
        printf("%d\n",arr[i]) ;
    }
}

七、左值与右值:在C语言中表示位于赋值运算符两侧的两个值,左边的就叫左值,右边的就叫右值。
1、左值
L-value中的L指的是Location,表示可寻址
左值表示存储在计算机内存的对象,相当于地址值,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作,因此左值可被赋值(常量除外),赋值中可以放在赋值操作符两边。 所有的引用都是左值。

2、右值(数组元素arr[i]可作)
R-value中的R指的是Read,表示可读
当一个符号或者常量放在操作符右边的时候,计算机就读取他们的“右值”,也就是其代表的真实值,右值相当于数据值 。右值是表达式的值(不是引用),可以放在赋值右面。

所有的左值都可以是右值,反之不成立

八、const 与 #define
1、const
编译运行阶段使用,
有具体的类型,在编译阶段会执行类型检查,
定义同时必须初始化,不可改变,
在内存中分配(可以是堆中也可以是栈中),在程序运行过程中只有一份拷贝
2、 #define
在预处理阶段展开,
没有类型,不做任何类型检查,仅简单宏替换,
不会分配内存,立即数,在内存中有若干个拷贝

猜你喜欢

转载自blog.csdn.net/qq_39191122/article/details/78832600