1.%#X
表示以十六进制形式输出,并附带前缀0X;
C语言用变量来存储数据,用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。
数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。
CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。如果程序不小心出错,或者开发者有意为之,在 CPU 要写入数据时给它一个代码区域的地址,就会发生内存访问错误。这种内存访问错误会被硬件和操作系统拦截,强制程序崩溃,程序员没有挽救的机会。CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
2.C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。char str[] = "http://c.biancheng.net";字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区(char *str = "http://c.biancheng.net";)。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。
- #include <stdio.h>
- int main(){
- char *str = "Hello World!";
- str = "I love C!"; //正确
- str[3] = 'P'; //错误
- return 0;
- }
3.用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。
4.其实,NULL 是在stdio.h
中定义的一个宏,它的具体内容为:#define NULL ((void *)0),从整体上来看,NULL 指向了地址为 0 的内存,而不是前面说的不指向任何数据。void *
表示一个有效指针,它确实指向实实在在的数据,只是数据的类型尚未确定
5.数组和指针不等价的一个典型案例就是求数组的长度,这个时候只能使用数组名,不能使用数组指针;数组也有类型,int a[6]对于数组 a,它的类型是int [6]
,表示这是一个拥有 6 个 int 数据的集合,1 个 int 的长度为 4,6 个 int 的长度为 4×6 = 24,编译器在编译过程中会创建一张专门的表格用来保存名字以及名字对应的数据类型、地址、作用域等信息,sizeof 是一个操作符,不是函数,使用 sizeof 时可以从这张表格中查询到符号的长度。
6.C语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof 或 & 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。
这种隐式转换意味着下面三种形式的函数定义是完全等价的:
- void func(int *parr){ ...... }
- void func(int arr[]){ ...... }
- void func(int arr[5]){ ...... }
7.
int (*p)[4] = a;
括号中的*
表明 p 是一个指针,它指向一个数组,数组的类型为int [4]
,这正是 a 所包含的每个一维数组的类型。int a[3][4];
指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:
- int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
- int (*p2)[5]; //二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别:指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。
8.一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。函数指针的定义形式为:returnType (*pointerName)(param list);returnType 为函数返回值类型,pointerName 为指针名称,param list 为函数参数列表。参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。
9.指针数组、二维数组指针、函数指针等几种较为复杂的指针,它们的定义形式分别是:
- int *p1[6]; //指针数组
- int *(p2[6]); //指针数组,和上面的形式等价
- int (*p3)[6]; //二维数组指针
- int (*p4)(int, int); //函数指针C语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。对,从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键!
10.int main(int argc, char *argv[]);argc 表示传递的字符串的数目,argv 是一个指针数组,每个指针指向一个字符串(一份数据)。包括程序名以及它后面的字符串都会被程序所接收。
11.
定 义 | 含 义 |
---|---|
int *p; | p 可以指向 int 类型的数据,也可以指向类似 int arr[n] 的数组。 |
int **p; | p 为二级指针,指向 int * 类型的数据。 |
int *p[n]; | p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]); |
int (*p)[n]; | p 为二维数组指针。 |
int *p(); | p 是一个函数,它的返回值类型为 int *。 |
int (*p)(); | p 是一个函数指针,指向原型为 int func() 的函数。 |