1、 未初始化和非法的指针
int *a;
*a = 12;
声明了一个名为a的指针变量,但没有对它进行初始化,所以没办法预测12将存储在什么地方。
无论是静态还是动态,声明一个指向整型的指针都不会创建用于存储整型值的内存空间。
如果运气好,a的初始值是一个非法地址,这样赋值语句将会出错,从而终止程序;
更严重的是,如果指针指向一个合法地址,位于那个位置的值将被修改。
注:声明一个指针变量并不会自动分配任何内存。在对指针执行间接访问前,指针必须进行初始化;或者使它指向现有的内存,或者给它分配动态内存。
2、 NULL指针
NULL指针作为一个特殊的指针变量,表示不指向任何东西。对一个NULL指针进行解引用操作是非法的。常见的两个后果分别是返回内存位置零的值以及终止程序。
3、int a;
int *d = &a;
*d= 10 - *d; //右边的作为右值使用,指的是d所指的位置所存储的值;左边作为左值使用,表示把右边计算的结果赋值给d所指向的位置
d= 10 - *d; //非法,因为它表示把整型数存储于一个指针变量
4、*&a = 25;
表示把25赋值给变量a;
&操作符产生变量a的地址,它是一个指针常量
5、指针常量
*100 = 25;错误,间接访问操作符*只能作用于指针类型表达式。如果确实是想把25存储于位置100,必须使用强制类型转换:
*(int *)100 = 25;
6、char ch = ‘a’;
char *cp = &ch;
ch作为右值使用时,表达式的值为‘a’,作为左值使用时,就表示该内存的地址;
&ch作为右值表示变量ch的地址,而它不能当做左值使用;因为对&ch进行求值时,无法知道它的结果应该存储于计算机的什么地方,并未标识任何机器内存的特定位置,所以它不是一个合法的左值;
&操作符的结果是个右值,它不能当作左值使用。
cp作为右值的时候,就是cp的值;作为左值的时候,就是cp所处的内存位置;
&cp作为右值,表示指针变量的地址;同样,不能作为左值;
加入间接访问操作符*
*cp作为右值时表示cp指向的变量的值,作为左值时表示指向的变量位置;
*的优先级高于+
*cp + 1,表达式表示‘b’,但并不知道它的存储位置,所以不是一个合法的左值;
+ 的结果不能作为左值。
间接访问指定了一个特定的内存位置,这样我们可以把间接访问表达式的结果作为左值使用;
间接访问操作符是少数几个其结果为左值的操作符之一;
++cp和cp++都不是合法的左值;但如果增加了间接访问操作符,它们就可以成为合法的左值了。
*++cp作为左值,指向ch后面的内存位置;
++的优先级高于*
*cp++它的右值和左值分别是变量ch的值和ch的内存位置;其过程是:(1)++操作符产生cp的一份拷贝;(2)然后++操作符增加cp的值;(3)最后,在cp拷贝上执行间接访问操作;
++*cp即*cp = *cp +1;表示cp所指位置的值增加1
7、对指针执行间接访问操作所产生的值也是个左值,因为这种表达式标识了一个特定的内存位置。
8、指针运算只有作用于数组中其结果才是可以预测的。对任何并非指向数组元素的指针执行算术运算是非法的。如果一个指针减去一个整数后,运算结果产生的指针所指位置在数组的第一个位置之前,那么它也是非法的。加法运算稍有不同,如果结果指针指向数组最后一个元素后面的那个内存位置仍是合法的(但不能对这个指针执行间接访问操作),不过再往后就不合法了。
9、数组名的值是一个指针常量,也就是数组第1个元素的地址。它的类型取决于数组元素的类型,如果它们是int型,那么数组名的类型是指向int的常量指针。只有两种场合下,数组名并不用指针常量表示——就是当数组名作为sizeof操作符或单目操作符&的操作数时。Sizeof返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量值得指针。
int a[10];
int *c;
a = c;这里的a是指针常量,不能被修改,所以这个赋值是非法的。
10、int array[10];
int *ap = array+2;
C的下标引用和间接访问表达式是一样的。*ap等于ap[0]、*(ap+6)等于ap[6];
ap[-1],下标引用就是间接访问表达式,对应为:*(ap+(-1))
11、由于下标引用可以作用于任意的指针,而不仅仅是数组名。因此C的下标检查所涉及的开销比你想象的要多。因此,编译器通常没有进行下标检查,即使提供了下标检查,编译器也会再提供一个开关,允许你去掉下标检查。
12、2[array]是合法的,等效:*(2+(array)),和表达式array[2]的值相等,这个诡异技巧之所以可行,缘于C实现下标的方法,但是你绝不应该编写2[array],因为会大大影响程序的可读性。
13、int a[5];
int *b;
声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。
上述声明之后,表达式*a是完全合法的,而*b是非法的。*b将访问内存中某个不确定的位置,或者导致程序终止。b++可以通过编译,但a++却不行,因为a的值是个常量。
14、如果想把一个数组名参数传递给函数,正确的函数形参有两种:
int strlen( char*string ) ;
int strlen( charstring [ ]);
sizeof(string)的值是指针的长度,而不是数组的长度。
15、自动计算数组长度
int vector[5] = {1,2,3,4,5};
int vector[] = {1,2,3,4,5};
如果生命中没有给出数组的长度,编译器把长度设置为刚好能够容纳初始值的长度。如果初始值裂变经常修改,这个技巧尤其有用。
16、字符数组的初始化
char message[] = { ‘ h ’ , ’ e ’ , ’ l ’ , ’l ’ , ’ o ’ };
快速方法初始化字符数组:char message [] = “hello”;
看上去是字符串常量,实际上并不是。
char *message2 = “hello”;才是字符串常量。
字符数组和字符串常量他们之间的区别只是在于这个字符串能不能修改,对于char message[] = "hello";编译器分配了一个长度为6的空间并初始化了{'H','e', 'l', 'l', 'o', ‘\0’ };我们可以通过message[i]进行访问更改,而对于char *message2 = "Hello";来说message2只是一个字符串指针,实际上是内存里面有”hello“这个常量,message2只是提供一个访问路径而已,并不能对这块内存进行修改,因为编译器将这块内存视为只读的。
17、指针数组:存放指针的数组
int* array[10];
18、不受限制的字符串函数
复制字符串:char *strcpy( char *dst, charconst *src);
连接字符串:char *strcat( char *dst , char const *src);
字符串比较:int strcmp(char const *s1, char const *s2);
长度受限的字符串函数
strncpy strncat strncmp;
查找一个字符:char *strchr (char const *str ,int ch);
查找一个子串:char *strstr(char const *s1, char const *s2);
19、union{
inti;
float f;
char *s;
}x = { 5 };
联合的长度就是它最长成员的长度。联合变量可以被初始化,但这个初始值必须是联合第1个成员的类型
20、函数指针
int f ( int );
int (*pf )( int ) =&f;
使用下面三种方式调用函数:
int ans;
ans = f( 25 );
ans = ( *pf )( 25 );
ans = pf( 25 )