1.指针:指针在本质上也是一个变量
指针需要占用一定的内存空间,不同类型指针占用的内存空间大小相同
指针用于保存内存地址的值
可以通过指针修改内存中的任意地址内容
2. *号的意义
在指针声明时,*号表示所声明的变量为指针
在指针使用时,*号表示取指针所指向的内存空间中的值
*号类似一把钥匙,通过这把钥匙可以打开内存,读取内存中的值
3. 传值调用与传址调用
指针是变量,因此可以声明指针参数
当一个函数体内部需要改变实参的值,则需要使用指针参数
函数调用时实参值将复制到形参
指针适用于复杂数据类型作为参数的函数中
4. 数组:相同类型的变量的有序集合
数组在一片连续的内存空间中存储元素
数组元素的个数可以显示或隐式指定
数组的空间大小为sizeof(array_type) * array_size
5. 数组地址与数组名
数组名代表数组首元素的地址
数组的地址需要用取地址符&才能得到
数组首元素的地址值与数组的地址值相同
数组首元素的地址与数组的地址是两个不同的概念
6数组名的盲点
数组名可以看做一个指向数组第一个元素的常量指针,在表达式中数组名只能作为右值使用,不能作为左值
数组名“指向”的是内存中数组首元素的起始位置
数组名作为sizeof操作符和&运算符的参数时不能看做常量指针
数组名其实并不是指针,在外部声明时不能混淆
7.指针运算:
①当指针p指向一个同类型的数组的元素时:p+1将指向当前元素的下一个元素;p-1将指向当前元素的上一个元素
②指针之间只支持减法运算,且必须参与运算的指针类型必须相同
p1 – p2; <--> ( (unsigned int)p1 -(unsigned int)p2) / sizeof(type);
注意:
只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差 当两个指针指向的元素不在同一个数组中时,结果未定义③指针也可以进行关系运算< <= > >=,前提是同时指向同一个数组中的元素
任意两个指针之间的比较运算(==,!=)无限制
8. 下标 VS 指针
从理论上而言,当指针以固定增量在数组中移动时,其效率高于下标产生的代码
当指针增量为1且硬件具有硬件增量模型时,表现更佳
现代编译器的生成代码优化率已大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可读性和代码维护的角度来看,下标形式更优
9. a和&a的区别
a为数组是数组首元素的地址
&a为整个数组的地址
a和&a的意义不同其区别在于指针运算
a + 1 –>(unsigned int)a + sizeof(*a)
&a + 1 ->(unsigned int)(&a) +sizeof(*&a)
10. C语言中,数组作为函数参数时,编译器将其编译成对应的指针
void f(int a[]); -> void f(int* a);
void f(int a[5]); -> voidf(int* a);
一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小
11. 指针和数组的对比
数组声明时编译器自动分配一片连续内存空间
指针声明时只分配了用于容纳指针的4字节空间
在作为函数参数时,数组参数和指针参数等价
数组名在多数情况可以看做常量指针,其值不能改变
指针的本质是变量,保存的值被看做内存中的地址
12. 字符串:从概念上讲,C语言中没有字符串数据类型
C语言中使用字符数组来模拟字符串
C语言中的字符串是以’\0’结束的字符数组
C语言中的字符串可以分配于栈空间,堆空间或者只读存储区
例:char* s1="hello";//只读存储区分配空间
char* s2=(char*)malloc(6*sizeof(char))//堆空间
13. 字符串长度
字符串的长度就是字符串所包含字符的个数
C语言中的字符串长度指的是第一个’\0’字符前出现的字符个数
C语言中通过’\0’结束符来确定字符串的长度
strlen的返回值是用无符号数定义的,因此相减不可能产生负数,因此strlen(a)>=strlen(b) ≠ strlen(a)-strlen(b)>=0
14. 不受限制的字符串函数
不受限制的字符串函数是通过寻找字符串的结束符’\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’。
strcpy和strcat必须保证目标字符数组的剩余空间足以保存整个源字符串。
strcmp以0值表示两个字符串相等
第一个字符串大于第二个字符串的时候返回值大于0
第一个字符串小于第二个字符串的时候返回值小于0
strcmp不会修改参数值,但依然以’\0’作为结束符
15. 长度受限的字符串函数
长度受限的字符串函数接收一个显示的长度参数用于限定操作的字符数
字符串复制:char* strncpy(char* dst, const char* src, size_t len);
字符串连接:char* strncat(char* dst, const char* src , size_t len);
字符串比较:int strncmp(const char* s1, const char*s2 , size_t len);
注意:
strncpy只复制len个字符到目标字符串
当源字符串的长度小于len时,剩余的空间以’\0’填充。
当源字符串的长度大于len时,只有len个字符会被复制,且它将不会以’\0’结束。
strncat最多从源字符串中复制len个字符到目标串中
strncat总是在结果字符串后面添加’\0’
strncat不会用’\0’填充目标串中的剩余空间
strncmp只比较len个字符是否相等
16. 使用一条语句实现strlen:(递归、三目运算符)
size_tstrlen(const char* s)
{
return(assert(s),(*s?(strlen(s+1)+1):0));
}
17. 实现库函数strcpy
char* strcpy(char* dst,const char*src)
{
char* ret = dst;
assert(dst&&src);
while((*dst++=*src++)!='\0');
return ret;
}
18.一般情况下,千万不要自行编写C标准库已经提供的函数。
标准库有时会使用汇编语言实现,目的就是为了充分利用机器所提供的特殊指令以追求最大的速度。
复用已经存在的函数库会更高效
19. 数组类型: C语言中的数组有自己特定的类型
数组的类型由元素类型和数组大小共同决定,例:int array[5]的类型为int[5]
定义数组类型
C语言中通过typedef为数组类型重命名:typedeftype(name)[size];
数组类型:
typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
数组定义:
AINT5 iArray;
AFLOAT10 fArray;
20. 数组指针
数组指针用于指向一个数组
数组名是数组首元素的起始地址,但并不是数组的起始地址
通过将取地址符&作用于数组名可以得到数组的起始地址
可通过数组类型定义数组指针:ArrayType* pointer;
也可以直接定义:type(*pointer)[n];
pointer为数组指针变量名
type为指向的数组的类型
n为指向的数组的大小
21. 指针数组
指针数组是一个普通的数组
指针数组中每个元素为一个指针
数组指针的定义:type* pArray[n];
type*为数组中每个元素的类型
pArray为数组名
n为数组大小
对比:
数组指针本质上是一个指针,指向的值是数组的地址
指针数组本质上是一个数组,指针数组中每个元素的类型是指针
22. 指向指针的指针
指针变量在内存中会占用一定的空间
可以定义指针来保存指针变量的地址值
例:
int main()
{
int a=0;
int* p=NULL;
int** pp=NULL;
pp=&p;//pp指向p
*pp=&a;//p指向a
}
为什么需要指向指针的指针:
指针在本质上也是变量
对于指针也同样存在传值调用与传址调用
23. 二维数组:
二维数组在内存中以一维的方式排布
二维数组中的第一维是一维数组
二维数组中的第二维才是具体的值
二维数组的数组名可看做常量指针
24. 数组名
一维数组名代表数组首元素的地址:int a[5] a的类型为int*
二维数组名同样代表数组首元素的地址:int m[2][5] m的类型为int(*)[5]
结论:
①二维数组名可以看做是指向数组的常量指针
②二维数组可以看做是一维数组
③二维数组中的每个元素都是同类型的一维数组
25.小结:
C语言中只有一维数组,而且数组大小必须在编译期就作为常数确定
C语言中的数组元素可是任何类型的数据,即数组的元素可以是另一个数组
C语言中只有数组的大小和数组首元素的地址是编译器直接确定的
26.C语言中的数组参数会退化为指针:
C语言中只会以值拷贝的方式传递参数
当向函数传递数组时,将数组名看做常量指针传数组首元素地址
C语言以高效为最初设计目标,在函数传递的时候如果拷贝整个数组执行效率将大大下降
二维数组参数同样存在退化的问题
二维数组可以看做是一维数组
二维数组中的每个元素是一维数组
二维数组参数中第一维的参数可以省略
void f(int a[5]);
void f(int a[]);
void f(int* a);
void g(int a[3][3]);
void g(int a[][3]);
void g(int (*a)[3]);
27. C语言中无法向一个函数传递任意的多维数组
为了提供正确的指针运算,必须提供除第一维之外的所有维长度限制
一维数组参数–必须提供一个标示数组结束位置的长度信息
二维数组参数–不能直接传递给函数
三维或更多维数组参数–无法使用
28. 函数类型
C语言中的函数有自己特定的类型 函数的类型由返回值,参数类型和参数个数共同决定例:int add(int i, int j)的类型为int(int, int)
C语言中通过typedef为函数类型重命名typedef type name(parameter list)
例:typedef int f(int, int);
typedef void p(int);
29. 函数指针
函数指针用于指向一个函数
函数名是执行函数体的入口地址
可通过函数类型定义函数指针:FuncType* pointer;
也可以直接定义:type(*pointer)(parameter list);
pointer为函数指针变量名
type为指向函数的返回值类型
parameter list为指向函数的参数类型列表
30. 回调函数
回调函数是利用函数指针实现的一种调用机制
回调机制原理
调用者不知道具体事件发生的时候需要调用的具体函数
被调函数不知道何时被调用,只知道被调用后需要完成的任务
当具体事件发生时,调用者通过函数指针调用具体函数
回调机制的将调用者和被调函数分开,两者互不依赖
1. 从最里层的圆括号中未定义的标示符看起
2. 首先往右看,再往左看
3. 当遇到圆括号或者方括号时可以确定部分类型,
并调转方向
4. 重复2,3步骤,直到阅读结束