C语言——指针
1. 指针
首先看一个指针的基本示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main () { char a = 'F' ; int f = 123 ; char *pa = &a; int *pb = &f; printf ("a = %c\n" , *pa); printf ("f = %d\n" , *pb); *pa = 'C' ; *pb += 2 ; printf ("a = %c\n" , *pa); printf ("f = %d\n" , *pb); printf ("size of pa is %d\n" , sizeof (pa)); printf ("size of pb is %d\n" , sizeof (pb)); printf ("address of a is %p\n" , pa); printf ("address of f is %p\n" , pb); return 0 ; }
运行结果如下:可以看到我的电脑是64位的,地址长度为8个字节
注意避免使用野指针
上面这种写法,首先定义了一个指针变量,但是没有赋初值,就是我们常说的野指针 。a 指针随机指向了一个地址,这个地址可能是系统重要文件的地址,通过赋值语句,若强行修改了系统重要文件,将带来很多的麻烦。
虽然现在的系统都有保护,但是尽量避免出现野指针。
2. 指针和数组
数组名就是数组第一个元素的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main () { char str[128 ]; printf ("输入字符串:" ); scanf ("%s" , str); printf ("str = %s\n" , str); printf ("str 的地址是:%p\n" , str); printf ("str[0] 的地址是:%p\n" , str); return 0 ; }
运行结果如下:
使用指针访问数组
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main () { char str[] = {'a' , 'w' , 'e' , 'l' , 'l' , 'f' , 'r' , 'o' , 'g' , }; char *p = str; printf ("str[1] = %c, str[2] = %c, str[3] = %c, str[4] = %c\n" , *(p + 1 ), *(p + 2 ), *(p + 3 ), *(p+4 )); return 0 ; }
运行结果如下:
指针 + 1 不是地址加1,聪明的编译器将代码翻译为地址偏移当前数据类型对应的空间大小
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> int main () { char str[] = {'a' , 'w' , 'e' , 'l' , 'l' , 'f' , 'r' , 'o' , 'g' , }; int nums[] = {0 , 1 , 2 , 3 , 4 , 5 , 6 , }; char *p = str; int *p1 = nums; printf ("str[1] = %c, str[2] = %c, str[3] = %c, str[4] = %c\n" , *(p + 1 ), *(p + 2 ), *(p + 3 ), *(p+4 )); printf ("nums[1] = %d, nums[2] = %d, nums[3] = %d, nums[4] = %d\n" , *(p1 + 1 ), *(p1 + 2 ), *(p1 + 3 ), *(p1+4 )); return 0 ; }
运行结果如下:
利用数组遍历指针指向的内容
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main () { char *str = "I'am a wellfrog, but never give up climbing up!" ; int i, length = strlen (str); for (i = 0 ; i < length; i++) { printf ("%c" , str[i]); } return 0 ; }
运行结果如下:
3. 指针与二维数组
4. NULL 与 void
void
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int main () { int num = 1024 ; int *pi = # char *ps = "Awellfrog" ; void *pv; pv = pi; printf ("pi:%p, pv:%p\n" , pi, pv); printf ("pv:%d\n" , *pv); ps = pv; printf ("ps:%p, pv:%p\n" , ps, pv); printf ("pv:%s\n" , pv); return 0 ; }
1 2 3 4 5 6 7 8 9 ''' pv = pi; printf ("pi:%p, pv:%p\n" , pi, pv); printf ("pv:%d\n" , *(int *)pv); ps = pv; printf ("ps:%p, pv:%p\n" , ps, pv); printf ("pv:%s\n" , pv); '''
NULL
NULL 的定义是指向位置 0 的 void 类型指针,计算机中 0 位置一般会被置为空,即 NULL 是指向空,或没有意义的意思。
1 #define NULL ((void *)0)
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main () { int *p1 = NULL ; int *p2; printf ("p1 = %p, p2 = %p\n" , p1, p2); printf ("%d\n" , *p1); return 0 ; }
可以看到这里的 gcc 编译器精明的将野指针初始化为地址 0。而打印该无效内容地址下的内容,计算机则会出现问题,停止工作。
NULL 不是 NUL('\0'
)。
NULL 用于指针和对象,表示控制,指向一个不被使用的地址。
'\0'
表示字符串的结尾。
5. 指向指针的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main () { int num = 520 ; int *p = # int **pp = &p; printf ("num:%d\n" , num); printf ("*p:%d\n" , *p); printf ("**p:%d\n" , **pp); printf ("&p:%p, pp:%p\n" , &p, pp); printf ("&num:%p,p:%p,*pp:%p\n" , &num, p, *pp); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <stdio.h> int main () { char *cBooks[] = { "《C语言程序设计》" , "《C primer plus》" , "《C 与指针》" , "《带你学 C 带你飞》" , }; char **byFishC; char **classicBooks[3 ]; int i; byFishC = &cBooks[3 ]; classicBooks[0 ] = &cBooks[0 ]; classicBooks[1 ] = &cBooks[1 ]; classicBooks[2 ] = &cBooks[2 ]; printf ("FishC出版的:%s\n" , *byFishC); printf ("经典书籍:\n" ); for (i = 0 ; i < 3 ; i++) { printf ("%s\n" , *classicBooks[i]); } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main () { int array [][4 ] = { {0 , 1 , 2 , 3 }, {4 , 5 , 6 , 7 }, {8 , 9 , 10 ,11 },}; int **p = array ; int i, j; printf ("p: %p, array: %p\n" , p, array ); printf ("p+1: %p, array + 1: %p\n" , p + 1 , array + 1 ); return 0 ; }
二维指针不知道每行有几列,所以不能直接使用列指针访问。
1 2 3 ''' int (*p)[4 ] = array ; '''
所以可以使用列指针搭配行指针遍历数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int main () { int array [][4 ] = { {0 , 1 , 2 , 3 }, {4 , 5 , 6 , 7 }, {8 , 9 , 10 ,11 },}; int (*p)[4 ] = array ; int i = 0 , j = 0 ; for (i = 0 ; i < 3 ; i++) { for (j = 0 ; j < 3 ; j++) { printf ("%2d " , *(*(p + i) + j)); } } return 0 ; }
还可以骚一点,这样就离被开除不远啦!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int main () { int array [][4 ] = { {0 , 1 , 2 , 3 }, {4 , 5 , 6 , 7 }, {8 , 9 , 10 ,11 },}; int (*p)[3 ][4 ] = &array ; int i = 0 , j = 0 ; for (i = 0 ; i < 3 ; i++) { for (j = 0 ; j < 3 ; j++) { printf ("%2d " , *(*(*p + i) + j)); } } return 0 ; }
6. 常量和指针
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main () { const float pi = 3.14 ; printf ("pi = %f\n" , pi); return 0 ; }
但是如果对 const
修饰的变量进行修改,则编译器报错。
1 2 3 4 ''' const float pi = 3.14 ;pi = 5 ; '''
指向常量的指针: const int *p = NULL;
常量指针: int * const p = NULL;
指向常量的常量指针不能修改常量的值
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main () { int num = 1 ; const int cnum = 2 ; const int *pc = &cnum; printf ("cnum:%d, &ccum:%p\n" , cnum, &cnum); printf ("*pc:%d, pc:%p\n" , *pc, pc); return 0 ; }
若修改指针指向的值,则错误:
1 2 3 4 5 6 7 ''' printf ("*pc:%d, pc:%p\n" , *pc, pc);*pc = 10 ; return 0 ;'''
指向常量的指针指向变量,不能通过常量指针修改变量的值,但是可以通过直接修改变量修改变量的值。
常量指针指向变量,常量指针本身不能被修改,但是可以通过指针修改变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main () { int num = 1 ; int num2 = 2 ; const int cnum = 2 ; int * const pc = # *pc = 10 ; printf ("*pc=%d, num=%d\n" , *pc, num); return 0 ; }
如果解注释修改常量指针,则报错。
常量指针本身还是一个 int
型指针,所以不能指向 const int
型变量。
1 2 3 int num = 1 ;const int cnum = 2 ;int * const pc = &cnum;
指向常量的常量指针:指针自身不能被修改,指针指向的值也不能被修改。
1 2 const int cnum = 1 ;const int * const p = &cnum;
指向“指向常量的常量指针”的指针。
1 2 3 const int cnum = 1 ;const int * const p = &cnum;const int * const *pp = &p;
或者按照如下书写方式,但是不好理解:
1 2 3 const int cnum = 1 ;const int const *p = &cnum;const int const **pp = &p;
7. 函数参数与指针
利用指针实现交换函数,通过传入实参的地址,直接修改实参。(如果只是简单的传入实参,利用形参交换,形参会在函数结束时被释放,而 main
中的实参未被修改。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> void swap (int *x, int *y) ; void swap (int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; } int main () { int a = 1 , b = 2 ; swap(&a, &b); printf ("a = %d, b = %d\n" , a, b); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> void get_array (int b[10 ]) ;void get_array (int b[10 ]) { b[1 ] = 520 ; printf ("sizeof(b) = %d\n" , sizeof (b)); printf ("b[1] = %d\n" , b[1 ]); } int main () { int a[10 ] = {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ,8 ,9 }; get_array(a); printf ("sizeof(a) = %d\n" , sizeof (a)); printf ("a[1] = %d\n" , a[1 ]); return 0 ; }
可以看到,传入的并不是整个数组,而是 8 位的数组首地址(64 位系统)。而在 main
中是整个数组 a[10]
十个整数的大小。
通过传入地址,可以修改原来数组中的实参值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> #include <stdarg.h> int my_sum (int n, ...) ;int my_sum (int n, ...) { int i, sum = 0 ; va_list vap; va_start(vap, n); for (i = 0 ; i < n; i++) { sum += va_arg(vap, int ); } va_end(vap); return sum; } int main () { int result; result = my_sum(3 , 1 , 2 , 3 ); printf ("result1 = %d\n" , result); result = my_sum(5 , 1 , 2 , 3 , 4 , 5 ); printf ("result = %d\n" , result); return 0 ; }
8. 指针函数与函数指针
指针函数: 返回值为指针的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> char *getWord (char ) ;char *getWord (char c) { switch (c) { case 'A' : return "Apple" ; case 'B' : return "Banana" ; case 'C' : return "Cat" ; case 'D' : return "Dog" ; default : return "None" ; } } int main () { char input; printf ("请输入一个字母: " ); scanf ("%c" , &input); printf ("%s\n" , getWord(input)); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 char *getWord (char c) { char str1[] = "Apple" ; char str2[] = "Banana" ; char str3[] = "Cat" ; char str4[] = "Dog" ; char str5[] = "None" ; switch (c) { case 'A' : return str1; case 'B' : return str2; case 'C' : return str3; case 'D' : return str4; default : return str5; } }
上面的程序返回一个独立的字符串可以正常执行,但是如果返回局部变量的字符数组则报错。此处显示不能返回函数中局部变量的地址,因为该局部地址将会在函数结束之后随函数一同被释放。
你肯定想问:为什么直接返回一个字符串就行呢?
那是因为字符串是一类特殊的存在,它会自己存储在一个特殊的区域,不会和局部变量一样立即消失。
函数指针: 指向函数的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> int square (int ) ;int square (int num) { return num * num; } int main () { int num; int (*fp)(int ); printf ("请输入一个参数: " ); scanf ("%d" , &num); fp = square; printf ("%d * %d = %d\n" , num, num, (*fp)(num)); return 0 ; }
上面的语句还可以修改为以下几种写法都可以正常运行,但是不推荐,因为不容易理解。
1 printf ("%d * %d = %d\n" , num, num, fp(num));
传入的参数是指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> int add (int , int ) ;int sub (int , int ) ;int calc (int (*)(int , int ), int , int ) ;int add (int a, int b) { return a + b; } int sub (int a, int b) { return a - b; } int calc (int (*fp)(int , int ), int a, int b) { return (*fp)(a, b); } int main () { printf ("3 + 5 = %d\n" , calc(add,3 ,5 )); printf ("3 - 5 = %d\n" , calc(sub, 3 , 5 )); return 0 ; }
更变态一点,再加一个 select ,即返回值为函数的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <stdio.h> int add (int a, int b) ;int sub (int a, int b) ;int calc (int (*)(int , int ), int , int ) ;int (*select(char ))(int , int ); int add (int a, int b) { return a + b; } int sub (int a, int b) { return a - b; } int calc (int (*fp)(int , int ), int a, int b) { return (*fp)(a, b); } int (*select(char op))(int , int ) { switch (op) { case '+' : return add; case '-' : return sub; } } int main () { int a, b; char op; int (*fp)(int , int ); printf ("请输入一个式子(例如1+3):" ); scanf ("%d%c%d" , &a, &op, &b); fp = select(op); printf ("%d %c %d = %d\n" , a, op, b, calc(fp, a, b)); return 0 ; }