无情的搬砖机器= =
一、指针 & 数组(细品)
指针就是指针,指针变量在32位系统下,永远占4字节,其值为某一个内存的地址。指针可以指向任何地方(内存),但是不是任何地方你都能通过这个指针变量访问到呢?(有地址,无[权限]空间)
数组就是数组,其大小与元素的类型和个数有关;定义数组时,必须指定其元素的类型和个数;数组可以存任何类型的数据,但是不能存函数。
总结如下:
- 指针本质上也是一个变量
- 指针要占用一定的内存空间(任何类型的指针的内存大小是一样的)
- 指针用于保存内存地址的值
数组&结构体
- 数组:所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙
- 结构体:在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个”填充字节”,这就导致各个成员之间可能会有若干个字节的空隙。
二、数组
2.1 数组的基础
- 数组在一片连续的内存中存储元素
- 元素的个数可以是显示的也可以是隐式的
1
int a[5]={1,2};//其中,其他的元素都为0来填充
2.2 数组地址与数组名
- 数组名代表数组首元素的地址
- 数组的地址需要取地址才可以得到
- 数组首元素的地址值 与 数组的地址值 相同(注意是值)
- 数组首元素的地址
&a[0]
与数组的地址&a
是两个不同的概念
举例,湖南省的省政府在长沙,长沙的市政府也在长沙,两个政府都在长沙。
2.3 数组名的盲点
数组名如何理解呢?用来存放数组的区域是一块在栈中静态分配的内存(非static),而数组名是这块内存的代表,它被定义为这块内存的首地址。这就说明了数组名是一个地址,而且,还是一个不可修改的常量,完整地说,就是一个地址常量。
数组名跟枚举常量类似,都属于符号常量。数组名这个符号,就代表了那块内存的首地址。注意了!不是数组名这个符号的值是那块内存的首地址,而是数组名这个符号本身就代表了首地址这个地址值,它就是这个地址,这就是数组名属于符号常量的意义所在。
由于数组名是一种符号常量,因此它是一个右值,而指针,作为变量,却是一个左值,一个右值永远都不会是左值,那么,数组名永远都不会是指针!
- 数组名可以看作一个常量指针(指针所指向的内容不能改变)
- 既然是指向常量,那就不能作为表达式左值,因为左值能被修改
- 可以单独修改数组元素,但用数组名(实际上是符号常量)不能直接修改数组
2.4 指针 & 数组 的定义与声明(经典错误)
2.4.1 定义为指针,声明为数组
1 | //test1 文件1 |
2.4.2 定义为数组,声明为指针
1 | //test1 文件1 |
2.5 定义数组类型
C语言中,可以通过typedef为数组类型重命名
1 | typedef type(name)[size]; |
数组类型(重命名了一种一个数组类型):
1 | typedef int(AINT5)[5]; |
数组定义:
1 | AINT5 i = {0,1,2,3,4}; |
三、 字符串
- 从概念上讲,C语言中没有字符串数据类型
- 在C语言中使用字符数组来模拟字符串
- 单引号
' '
括起来的一个字符代表整数 - 双引号
" "
括起来的字符(串)代表一个指针
- 单引号
- 字符串是以
'\0'
结束的字符数组 - 字符串可以分配于栈空间,堆空间或者 字符常量区(不能被改变)
1 | char* s1 = "Hello World"; //在字符常量区,不可改变这个字符串中的字符 |
3.1 (实)字符数组、(虚)字符串
下面的代码合法吗?使用它有什么隐患?
1 | char a[3] = "abc"; |
在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的字符数组,初始化为“abc”
;注意,它没有通常的字符串终止符'\0'
,因此这个数组只是看起来像C语言中的字符串,实质上却不是。因此所有对字符串进行处理的函数,比如strcpy、printf
等,都不能够被使用在这个假字符串上。
3.2 字符串的长度
- 字符串的长度就是字符串说包含字符的个数
- C语言中的字符串的长度值得是第一个
'\0'
字符串出现的字符个数 - C语言中通过
'\0'
结束来确定字符串的长度
以strlen()
为例,strlen()
为无符号类型
1 | //比较两个字符串的长度的时候 |
3.3 不受限制的字符串函数
不受限制的字符串函数是通过寻找字符串得结束符 '\0'
来判断长度。
- 字符串复制函数:
char* strcpy(char* dst,const char* src)
- 字符串连接:
char* strcat(char* dst,const char* src)
- 字符串比较函数:
int strcmp(const char* s1,const char* s2)
注意事项
- 不受限制的字符串函数都是以
'\0'
作为结束标记来进行的,因此输入参数必须包含'\0'
- strcat和strcpy必须保证目标字符数组的剩余空间足以保存整个源字符串
- strcmp以0值表示两个字符串相同
- 第一个字符串大于第二个字符串的时候返回值大于0
- 第一个字符串小于第二个字符串的时候返回值小于0
- strcmp不会修改参数值,但依然以
'\0'
作为结束符号
3.4 长度受限制的字符串函数
长度受限的字符串函数,是接收一个长度参数用于限定操作的字符串,提高程序的安全性。
- 字符串复制:
char* strncpy
- 字符串连接:
char* strncat
- 字符串比较:
char* strncmp
四、数组参数和指针参数(针对函数传入)
数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:
- 不允许拷贝数组
- 使用数组时通常会将其转换成指针
因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。
因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。(数组参数退化为指针)
尽管不能以值传递的形式传递数组,但是我们可以把形参写成类似数组的形式:
1 | //尽管形式不同,但这三个进行print函数是等价的 |
当编译器处理对print
函数的调用时,只检查传入的参数是否是const int*
类型:
1 | int i = 2; |
如果我们传给printf函数的是一个数组,则实参自动地转换成指向首元素的指针,数组的大小对函数的调用没有影响。
五、二维数组
5.1 二维数组参数和指针参数
C语言的编译器会让(不论是一维数组还是二维数组)数组参数退化为指针。C语言中无法向一个函数传递任意的多维数组。
- 二维数组参数同样存在退化问题。
- 二维数组可以看作是一维数组
- 二维数组中的每一个元素是一维数组
- 二维数组参数中第一维的参数可以省略(退化过程)
注意事项
- C语言中无法向一个函数传递任意的多维数组
- 为了提供正确的指针运算,必须提供除一维之外的所有维的长度(程序写法)
限制- 一维数组-必须提供结束的标志
- 二维数组-不能直接传递给函数
- 多维-无法使用
1 |
|
5.2 二维数组与数组指针
- 二维数组在内存中以一维的方式排布
- 二维数组中的第一维是一维数组
- 二维数组中的第二维才是具体的值
- 二维数组的数组名同样可以看作常量指针
- 二维数组同样代表数组首元素的地址
1 |
|
答案为-4,因为(*p)一次跨越4个,而a一次跨越5个
1 | //用指针遍历二维数组 |
六、指向指针的指针
为什么需要指向指针的指针?
- 指针在本质上也是一个变量
- 对于指针来讲也有传值调用与传址调用
1 | //重置动态空间 |