[C和指针]第二部分
第四章 指针... 1
第五章 函数... 14
第六章 数组... 17
第七章 字符(串)/节... 25
第四章 指针
指针图示:
注意箭头起始于方框内部,因为它代表存储于该变量的值。同样,箭头指向一个位置,而不是存储于该位置的值,这种记法提示跟随箭头执行间接访问操作的结果将是一个左值(即某个变量的地址,如&a,而不是某个变量a的值)。
int *a;
*a =12;
上面的程序是极为危险与错误的。这个声明创建了一个名叫a的指针变量,后面那个赋值语句把12存储在a所指向的内存位置。因为从未对a进行初始化,所以我们没有办法预测12这个值将存储于什么地方。虽然如果变量a是静态的它会被初始化为0,变量为自动的,它根本不会被初始化,但无论是哪种情况,
a = &b;//初始化指针,让它指向某个确定的内存空间
printf("%d\n", a);//现在指针所指向的位置为:2293576
*a = 12;//将12常量存储到a所指向的某个内存位置
printf("%d\n", *a);//12
printf("%d", b);//12
NULL指针不指向任何东西。要使指针变量为NULL,你可以给它赋一个零。为了测试一个指针变量是否为NULL,你可将它与零值进行比较。之所以选择零这个值是因为一种约定,就不同机器内部而言,NULL指针实际值可能不是零,在这种情况下,只要使用NULL,编译器将负责零值和内部值之间的翻译转换工作。
如果对一个NULL指针进行间接访问会发生什么情况呢?结果会因编译器而异,在有些机器上,它会访问位置为零的地址中的内容,但这是极其错误的;在其他机器上,对NULL指针进行间接访问将引发一个错误,并终止程序,这种情况下程序员就很容易提示发现错误,如XP就是这样的:
//将字符数组(字符串就是数组)首地址赋给p1
char *p1="a" ;
printf("%c",*p1);//a
//将指针指向内存地址为0的位置
char *p2=NULL ;
printf("%c",*p2);//!!出错
int a;
*&a=12;
上面程序的结果就是将12赋值给变量a。&操作符产生变量a的地址,它是一个指针常量,接着,*操作符访问其操作数所表示的地址,在这个表达式中,操作数是a的地址,所以25就存储到a中。这与 a = 12 有区别吗?从结果上讲没有区别,但它涉及到更多的操作。
假定a存储于位置100,下面这条语句合法吗?
*100 = 12;
这是错误的,因为字面值100的类型是整型值,而间接访问操作只能作用于指针类型表达式。如果你确实想把12存储于位置100,你必须使用强制类型转换:
从整型转换为指向整型的指针类型
虽然强转后合法,但通常我们不会这样作,因为我们无法预测编译器会把某个特定的变量放在内存中的什么位置,所以你无法预先知道变量a的地址。这个技巧除非用来访问内存中某个特定的位置,不是某个变量,而是访问硬件本身。
二级指针(指针的指针):
int a = 12;
int *b = &a;
int**c = &b;
表达式 | 等效的结果 |
a | 12 |
b | &a |
*b | a , 12 |
c | &b |
*c | b , &a |
**c | *b , a, 12 |
各种指针表达式示例:
char ch = 'a';
char *cp = &ch;
现在我们有了两个变量,它们初始化如下:
表达式 |
右值 |
左值 |
描述 |
,再拷贝,椭圆即为拷贝出来的内容。 |
|||
,椭圆即为拷贝出来的内容。 |
|||
int a[][3] = { { 11, 12,14 }, { 21, 22, 23 } }; 第五章 函数K&R C:
int * find_int(key,array,array_len) int key; int array; int array_len; {} 但不能省略函数的大括号,所以不支持函数原型声明。 为了与ANSI标准之前的程序兼容性,没有参数的函数的原型应该写成下面这样: int func(void); 当程序调用一个无法见到原型的函数时,编译器便认为该函数返回一个整型值。所有函数都应该具有原型,尤其是那些返回值不是整形的函数。 可变参数 可以使用<stdarg.h>中的一组宏定义来操纵可参数。 用法: (1)首先在函数里定义一具va_list型的变量,这个变量是指向参数的指针 (2)然后用va_start宏初始化变量刚定义的va_list变量,这个宏的第二个参数是省略号前最后一个有名字的参数。 (3)然后用va_arg返回可变的参数,va_arg的第二个参数是你要返回的参数的类型。如果函数有多个可变参数的,依次调用va_arg获取各个参数。 (4)最后用va_end宏结束可变参数的获取。 由于参数列表中的可变参数部分并没有原型,所以,所有作为可变参数传递给函数的值都将执行缺省参数类型提升()相乘。这个乘法需要花费一定的时间和空间。现使用指针来实现同样的功能: int array[10], *p; for (p = array; p < array + 10; p++) { *p = 0; } 现在这个乘法出现在for语句的调整部分,1这个值必须与整型的长度相乘,然后现与指针相加,但这里存在一个重大区别:循环每次执行时,执行乘法运算的都是两个相同的数(1和4),结果,这个乘法只在编译时执行一次,程序在运行时并不执行乘法运算,所以在运行时所需要的指令就少一些。 最高效的数组访问: #define SIZE 50 int x[SIZE]; int y[SIZE]; //数组拷贝 int main() { registerint *p1, *p2; for (p1 = x, p2 = y; p1 < &x[SIZE];) { *p1++ = *p2++; } } 结论:当你根据某个固定数目的增量在一个数组中移动时,不如使用指针变量将比使用下标产生效率更高的代码;声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高;如果你可以通过测试一些已经初始化并经过调整的内容来判断循环是否该应该终止,那么你就不需要使用一个单独的计数器。 指针和数组 int a[5]; int *b; 声明一个数组时,编译器将根据声明所指定元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为这个指针本身保留内存空间,它并不为任何整型值分配内存空间,而且,指针变量并未初始化为指向现有的有意义的空间。所以 *a 是完全合法的,但表达式 *b 却是非法的。*b将访问内存中某个不确定的位置,或者导致程序终止,另外,表达式b++可以通过编译,但a++却不行,因为a的值是个常量。 数组参数 数组名的值就是一个指向数组第一个元素的指针,所以传递给函数的是一份该指针的拷贝。函数如果执行了下标引用,实际上是对这个指针执行间接访问操作,并且通过这种间接访问,函数可以访问和修改主调程序的源数组元素。 传址调用是通过传递一个指向所需元素的指针,然后在函数中对该指针执行间接访问操作实现对数据的访问,而作为参数的数组名是个指针,下标引用实际执行的就是间接访问,所以传递数组时“好像”体现了传址,但本身却是传值,那数组的传值行为表现在什么地方呢?传递给函数的是参数的一份拷贝(指向数组起始位置的指针的拷贝),所以函数可以自由地操作它的指针形参,而不必提心会修改对应的作为实参的指针。所以并不矛盾:所有的参数都是通过传值方式传递的。 传递一个数组时,正确的函数形参应该是怎样的?它是应该声明为一个指针还是一个数组?调用函数时实际传递的是一个指针。但为了使用程序员新手更容易上手一些,编译器也接受数组形式的函数形参,下面两个函数原型是相等的: int strlen(char * string); int strlen(char string[]); 这个相等性暗示指针和数组名实际上是相等的,但千万不要被它糊弄了,这两个声明确实相等,但只是在当前这个上下文环境中。哪个“更加准确”呢?答案是指针,因为实际上是个指针,而不是数组,同样,表达式sizeof string的值是指向字符的指针的长度,而不是数组的长度: int size1(int * a){ returnsizeof a; } int size2(int a[]){ returnsizeof a; } int main() { int a[5]; printf("%d\n",sizeof a);//20 printf("%d\n",size1(a));//4 printf("%d\n",size2(a));//4 } 现在你应该清楚为什么函数原型中的一维数组形参无需写明它的元素数目(如果是多维数组,则也只能省略第一维的大小),因为函数并不为数组参数分配内存空间。形参只是一个指针,它指向的是已经在其他地方分配好的内存的空间,它可以与任何长度的数组匹配。另一方面,这种实现方法使函数无法知道数组的长度,如果函数需要知道数组的长度,它必须作为一个显示的参数传递给函数。 不完整的初始化 int vector[5] = { 1, 2, 3, 4, 5, 6 }; int vector[5] = { 1, 2, 3, 4 }; 第一个声明是错误的,我们没有办法把6个整型值装到5个整型变量中去,但第二个声明是合法的,最后一个元素初始化为0。另外,也只能省略最后元素的值,不能省略中间的。 自动计算数组长度 int vector[] = { 1, 2, 3, 4 }; 如果声明中并未给出数组的长度,编译器就把数组的长度设置为刚好容纳所有的初始值的长度。 字符数组的初始化 char string1[] = { 'H', 'e', 'l', 'l', 'o', 0 }; char string2[] = "Hello"; 第二种看上去是一个字符串常量,实际上并不是(只是一个初始化列表),它只是第一个声明的另一种写法。那什么情况下"Hello"是一个字符串常量呢?要根据它所处的上下文来区分:当用于初始化一个字符数组时,它就是一个初始化列表,在其他任何地坟,它都表示一个字符串常量。 char string1[] = "Hello"; char *string2 = "Hello"; 这两个初始化看上去很像,但它们具有不同的含义。第一个初始化一个字符数组的元素,而后者则是一个真正的字符串常量,这个指针变量被初始化为指向这个字符串常量的存储位置,并且这个位置的内容不能被修改: 多维数组 多维数组在内存分配上与一维数组一样,在物理上没有区别,也是连续的,只是多维数组在逻辑上将元素划分为多个逻辑单位。 int matrix[6][10]; int *mp; matrix[3][8] = 38; matrix[3][9] = 39; matrix[4][0] = 40; mp = &matrix[3][8]; printf("First value is %d \n", *mp);//38 printf("Second value is %d \n", *++mp);//39 printf("Third value is %d \n", *++mp);//40 这个例子使用一个指向整型的指针遍历存储了一个二维整型数组元素的内存空间,这个技巧被称为压扁数组,它实际上是非法的,因此从某行移到下一行后就无法回到包含第1行的那个子数组,尽管通常没有问题,但有可能的话还是应该避免这样使用。 多维数组名: int matrix[3][10]; matrix可以看作是一个一维数组,包含3个元素,只是每个元素又是包含了10整型元素的数组。matrix这个名字是一个指向它第1个元素的指针,所以matrix是一个指向一个包含10个整型元素的数组的指针。 matrix[1][5] == *(*(matrix + 1) + 5) == *(matrix[1] + 5) matrix 指向第一行,(matrix+1)指向第二行,*(matrix+1)代表第二行元素(第二维数组),既然*(matrix+1)又是一个数组,所以该表达式又代表了(matrix+1)指向的第二行子数组的首元素的地址,所以(*(matrix+1)+5)表示在(matrix+1)指向的第二行子数组中移动到第6个元素位置上,所以*(*(matrix+1)+5)代表了该位置上的元素。 int matrix[3,10]; 在其他语言中表示二维数组,但在C中不是的,它是一个逗号表达式,最终的结果为matrix[10],而不是二维数组了 int vector [10], *vp = vector; int matrix[3][10], *mp = matrix; 第一个表达式合法,vp与vector具有相同的类型,都是指向整型的指针;第二个声明非法,因为matrix并不是一个指向整型的指针,而是一个指向整型数组的指针,正确写法如下: int (*p)[10]= matrix; (*p)[10]由于间接访问*的优先级低于数组下标访问,所以使用括号,这样就使 *p[10]从数组类型转换成了一个指针类型,p是一个指向拥有10个整型元素的数组的指针,使用它可以在matrix数组中一行一行的移动。上述表达式指向了matrix数组的第一行。 如果你需要一个指针逐个访问整型元素而不是逐行在数组中移动,你应该这样: int *pi=&matrix[0][0]; //第一个元素的地址 int *pi=matrix[0];//第一个元素的地址也就是数组名 上面两个声明都创建了一个简单的整型指针,并以两种不同的方式进行初始化,指向matrix的第1个整型元素。 如果你打算在指针上执行任何指针运算,应该避免这种类型的声明: int (*p)[] = matrix; p仍然是一个指向整型数组的指针,但 数组的长度没有,当与某个整型运算时,它的值将根据空数组来进行调整,即与零乘,这不是我们想要的。有些编译器要以捕捉到这类错误,但有些编译器却不能。 int matrix[3][10]; 如果传递给一个函数,则函数的原型声明可以有以下两种: void func(int(*p)[10]); void func(int p[][10]); 但决不能是: void func(int **p); 因为**p的类型为指向指针的指针,而(*p)[10]表示指向一个具有10元素的数组的指针。 多维数组的初始化可以像一维数组那样: int matrix[2][3] = { 1, 2, 3, 4, 5, 6 }; 但Java不允许这样 charconst* keyword[] = { "do", "for", "if", "register", "return", "switch", "while" }; charconst**kwp = keyword; keyword是一个指针数组,数组名keyword是一个二级指针(每个元素都是指针),其内存结构如下: charconstkeyword[][9] = { "do", "for", "if", "register", "return", "switch", "while" }; 其内存结构如下: 第七章 字符(串)/节C语言并没有的字符串数据类型,因为字符串是以字符串常量的形式出现或者存储于字符数组中。个字符串不为空时才进行查找,如果s2为空,返回null
*/ if (*s2 != '\0') { /* ** 查找s2在s1中第一次出现的位置 */ current = strstr(s1, s2); /* ** 每次找到字符串时,指向它的起始位置,然后查找该字符串下一个匹配位置。 */ while (current != NULL) { last = current; current = strstr(last + 1, s2); } } /* ** 返回指向我们找到的最后一次匹配的起始位置的指针 */ return last; } 查找一个字符串前缀: size_t strspn(charconst *str, charconst *group); 返回str起始部分匹配group中任意字符的字符数。例如,如果group包含了空格、制表符等空白字符,这个函数将返回str起始部分空白字符数目。下面的代码将计算一个指向字符串中第一个非空白字符的指针: char * ptr = buffer + strspn(buffer, "\n\r\f\t\v"); size_t strcspn(charconst *str, charconst *group); strcspn正好与strspn相反,它对str字符串起始部分中不与group中任何字符匹配的字符进行计数。 示例: char buffer[] = "25,142,330,Smith,J,239-4123"; int len1, len2; len1 = strspn(buffer, "0123456789"); printf("%d\n", len1);//2 len2 = strcspn(buffer, "ith"); printf("%d\n", len2);//13 查找标记: char * strtok(char * str,constchar *set) strtok函数使用set字符串中的标记将字符串str分隔成不同的独立部分,每次可以返回这个独立部分。strtok函数找str中的标记后,使用NUL替换,并返回一个指向标记前的邻近字符串指针。如果函数的第一个参数str不为NULL,函数将查找字符串的第一个标记,如果为NULL,函数就从上一次查找的位置后开始查找标记。通常,第一次调用时传递一个字符串指针,以后,这个函数被重复调用(第一参数传递NULL),直到它返回NULL: void print_tokens(char *line) { staticchar whitespace[] = " ,"; char *token; for (token = strtok(line, whitespace); token != NULL; token = strtok(NULL, whitespace)) { printf("Next token is %s\n", token); } } int main() { char buffer[] = "a b,c"; print_tokens(buffer); } 由于strtok函数保存了处理过的函数的局部状态信息,所以 printf("%c\n", memchr(a, 'b', 3) == NULL ? '0' : *(char *) memchr(a, 'b', 3));//b memset函数把从aga vck r length个字节都设置为字符值ch,例如: memset(buffer, 0, SIZE); 把buffer的前个字节都初始化为0。 [C和指针]第二部分的更多相关文章
随机推荐
|