都说指针是C语言的精髓,那指针究竟有哪些经典应用场合呢?

指针有三大类:指向数据的指针,指向函数的指针和范型指针(void*)。

其经典的应用场合,可以分为以下10类:

1 与函数相关的使用

1.1 在函数中用作输出型参数,产生副作用(更新被调函数中的局部变量的值);

1.2 在函数中用作输出型参数,用于返回多个值;

1.3 在函数中用作输入型参数,指向复合类型,避免传值的副作用(性能损耗);

1.4 用做函数返回值,返回一个左值;

1.5 用于指向函数的函数指针,使用函数指针调用回调函数;

2 用于指向堆内存;

3 与void配合使用,用void*来表示一个泛型指针;

4 用于指向数组名(数组指针);

5 用于指向字符串常量(字符串常量指针);

6 在数据结构中,用作链式存储;

附加:在字符串、文件操作中跟踪操作位置;

1 与函数相关的使用

1.1 在函数中用作输出型参数,产生副作用(更新被调函数中的局部变量的值)

#include <stdio.h> void demo(int *ap,int size, int *max) { *max=ap[0]; for(int i=1;i<size;i ) if(ap[i]>*max) *max = ap[i]; } int main() { int max,ap[5]={1,2,8,4,5}; demo(ap,5,&max); printf("%d\n",max); getchar(); return 0; }

1.2 在函数中用作输出型参数,用于返回多个值

#include <stdio.h> #include <math.h> int equationSolve(double a,double b,double c,double *x1,double *x2) { int delta = a*a-4*a*c; if(delta>=0) { *x1 = (-b sqrt(delta))/2*a; *x2 = (-b-sqrt(delta))/2*a; return 1; } else return 0; } int main(void) { double x1,x2; if(equationSolve(1,3,-14,&x1,&x2)) printf("x1=%.2f\nx2=%.2f\n", x1,x2); else printf("无实根!\n"); getchar(); return 0; } /* x1=2.27 x2=-5.27 */

1.3 在函数中用作输入型参数,指向复合类型,避免传值的副作用(性能损耗)

#include <stdio.h> typedef struct Inventory{ int sku; char name[36]; char unit[12]; char supplier[48]; double price; double stock; }Inven; void demo(const Inven *p) { printf("The amounts is %f\n",p->price * (*p).stock); // …… } int main() { Inven inven={123,"carban fibre","kg","uc",128,100}; demo(&inven); getchar(); return 0; }

1.4 用做函数返回值,返回一个左值

#include <stdio.h> #include <malloc.h> #include <stdlib.h> void printIntArray(void** array, size_t length) { printf("Array at %p\n", array); while (length--) { printf(" [%zu] at %p -> %p", length, array length, *(array length)); if (*(array length)) { printf(" -> %d", *(int*)*(array length)); } printf("\n"); } } void* getElement(void** array, size_t index) { return *(array index); } int main(int argc, char** argv) { const size_t n = 5; size_t i; /* n element array */ void** test = malloc(sizeof(void*) * n); i = n; while (i--) { *(test i) = NULL; } /* Set element [1] */ int testData = 123; printf("testData at %p -> %d\n", &testData, testData); *(test 1) = (void*)&testData; printIntArray(test, n); /* Prints 123, as expected */ printf("Array[1] = %d\n", *(int*)getElement(test, 1)); getchar(); return 0; }

返回左值在C 中应用比较多,特别是用引用来返回左值,如返回ostream&,或重载[]、=等运算符。

1.5 用于指向函数的函数指针,使用函数指针调用回调函数

// 通用的冒泡排序函数的应用 #include <iostream> #include <cstring> using namespace std; template <class T> void sort(T a[], int size, bool (*f)(T,T)); // callee bool increaseInt(int x, int y) {return x<y;} // callbackee1 bool decreaseInt(int x, int y) {return y<x;} // callbackee2 bool increaseString(char *x, char *y) {return strcmp(x,y)<0;} // callbackee3 bool decreaseString(char *x, char *y) {return strcmp(x,y)>0;} // callbackee4 int main() // caller { int a[] = {3,1,4,2,5,8,6,7,0,9}, i; char *b[]= {"aaa","bbb","fff","ttt","hhh","ddd","ggg","www","rrr","vvv"}; sort(a, 10,increaseInt ); for (i = 0; i < 10; i) cout << a[i] <<"\t"; cout << endl; sort(a, 10, decreaseInt); for ( i = 0; i < 10; i) cout << a[i] <<"\t"; cout << endl; sort(b, 10, increaseString ); for (i = 0; i < 10; i) cout << b[i] <<"\t"; cout << endl; sort(b, 10, decreaseString); for ( i = 0; i < 10; i) cout << b[i] <<"\t"; cout << endl; while(1); return 0; } // 通用的冒泡排序函数 template <class T> void sort(T a[], int size, bool (*f)(T,T)) { bool flag; int i, j; for (i = 1; i < size; i) { flag = false; for (j = 0; j <size - i; j) { if (f(a[j 1], a[j])) { T tmp = a[j]; a[j] = a[j 1]; a[j 1] = tmp; flag = true; } } if (!flag) break; } } /* 0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0 aaa bbb ddd fff ggg hhh rrr ttt vvv www www vvv ttt rrr hhh ggg fff ddd bbb aaa */

2 用于指向堆内存

实质也是通过库函数(malloc.h)返回void*指针。

#include <stdio.h> #include <malloc.h> int** demo(int r,int c) { int **ap = (int**)malloc(sizeof(int*)*r); for(int i=0;i<c;i ) ap[i]=(int*)malloc(sizeof(int)*c); return ap; } int main() { int r=3,c=5; int* *ap=demo(r,c); int i,j; for(i=0;i<r;i ) for(j=0;j<c;j ) ap[i][j] = (i 1)*(j 1); for(i=0;i<r;i ) { for(j=0;j<c;j ) printf("- ",ap[i][j]); printf("\n"); } getchar(); return 0; } /* 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 */

3 与void配合使用,用void*来表示一个泛型指针

#include <stdio.h> #include <string.h> #include <stdlib.h> int swap2(void *x, void *y, int size) { void *tmp; if((tmp=malloc(size)) == NULL) return -1; memcpy(tmp,x,size); memcpy(x,y,size); memcpy(y,tmp,size); free(tmp); return 0; } int main() { int a=3,b=4; swap2(&a,&b,sizeof(int)); printf("%d %d\n",a,b); double c=3,d=4; swap2(&c,&d,sizeof(double)); printf("%f %f\n",c,d); getchar(); return 0; }

4 用于指向数组名(数组指针

#include <stdio.h> void funcP(int *p,int r,int c) { for(int i=0;i<r*c;i ) printf((i 1)%(r 1)==0 ? "-\n":"- ",*p ); printf("\n"); } void funcAp(int (*p)[4],int r,int c) // int p[][4] { for(int i=0;i<r;i ) { for(int j=0;j<c;j ) printf("- ",*(*(p i) j)); // p[i][j] printf("\n"); } printf("\n"); } void funcApp(int (*p)[3][4],int r,int c) { for(int i=0;i<r;i ) { for(int j=0;j<c;j ) printf("- ",*(*(*p i) j)); // (*p)[i][j], 体现解引用指针,产生副作用 printf("\n"); } printf("\n"); } int main() { int arr[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; int size = sizeof arr / sizeof *arr; // 在该上下文中,arr是数组的地址,其类型是int(*)[3][4] funcP((int*)arr,3,4); funcAp(arr,3,4); // 在该上下文中,arr是数组首元素的地址,其类型是int(*)[4] funcApp(&arr,3,4); // 在该上下文中,arr是数组的地址,其类型是int(*)[3][4] getchar(); return 0; } /* output: 0 1 2 3 4 5 6 7 8 9 10 11 0 1 2 3 4 5 6 7 8 9 10 11 0 1 2 3 4 5 6 7 8 9 10 11 */

5 用于指向一个字符串常量(字符串常量指针

const char* demo() { //char sa[] = "Hello!"; const char *sp = "Hello!"; return sp; }

关于字符数组和字符指针可以图示如下:

c语言中指针的总结(指针的10种经典应用场合)(1)

在字符指针数组,数组元素是一个字符指针,用于指向一个字符串常量,如:

char *pMonth[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; char *week[10] = { "Mon","Tue","Wed","Thu","Fri","Sat","Sun"}; char* color[]={"红-red","橙-orange","黄-yellow","绿-green","青-cyan","蓝-blue","紫-purple"}; char *gans[10] = {"甲","乙","丙","丁","戊","己","庚","辛","壬","癸"}; char* zhis[12] = {"子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"}; char* animals[12] = {"鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"};

6 在数据结构中,用作链式存储

#define ElementType int typedef struct LNode { ElementType data; struct LNode *next; } LNode, *LinkList;

附加:在字符串、文件操作中跟踪操作位置。

如分割字符串函数strtok():

char *strtok(char s[], const char *delim);

对该函数的一系列调用将str拆分为标记(token),这些标记是由分隔符中的任何字符分隔的连续字符序列。

在第一次调用时,函数需要一个C风格字符串作为str的参数,str的第一个字符用作扫描标记的起始位置。在随后的调用中,函数需要一个空指针,并使用最后一个标记结束后的位置作为扫描的新起始位置。

要确定标记的开头和结尾,函数首先从起始位置扫描未包含在分隔符中的第一个字符(它将成为标记的开头)。然后从这个标记的开头开始扫描分隔符中包含的第一个字符,它将成为标记的结尾。如果找到终止的空字符,扫描也会停止。

标记的末端将自动替换为空字符,函数将返回标记的开头。

在对strtok的调用中找到str的终止空字符后,对该函数的所有后续调用(以空指针作为第一个参数)都会返回空指针。

找到最后一个标记的点由函数在内部保留,以便在下次调用时使用(不需要特定的库实现来避免数据争用)。

#include <string.h> #include <stdio.h> int main() { char str[80] = "This is - www.runoob.com - website"; const char s[2] = "-"; char *token; /* 获取第一个子字符串 */ token = strtok(str, s); /* 继续获取其他的子字符串 */ while (token != NULL) { printf("%s\n", token); token = strtok(NULL, s); } printf("\n"); for (int i = 0; i < 34;i ) printf("%c", str[i]); return (0); }

二进制文件的随机读写:

在标记文件信息的结构体FILE中,包含3个标识文件操作位置的指针。

typedef struct _iobuf { char *_ptr; //文件操作的下一个位置 int _cnt; //当前缓冲区的相对位置 char *_base; //指基础位置(即是文件的其始位置) int _flag; //文件标志 int _file; //文件的有效性验证 int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取 int _bufsiz; //缓冲区大小 char *_tmpfname; //临时文件名 }FILE;

code demo:

#include<iostream> // 按记录分块读写文件 #include <fstream> #include<cstdlib> #include<cstring> using namespace std; class Student { public: Student(void) {} Student(int n, char nam[20], float s): num(n),score(s) { strcpy(name,nam); } void setNum(int n) { num=n; } void setName(char nam[20]) { strcpy(name,nam); } void setScore(float s) { score=s; } void show() { cout<<num<<" "<<name<<" "<<score<<endl; //显示通过<<的重载实现更自然 } private: int num; char name[20]; float score; }; int main( ) { Student stud[5]= { Student(1001,"Li",85), Student(1002,"Fun",97.5), Student(1004,"Wang",54), Student(1006,"Tan",76.5), Student(1010,"ling",96) }; fstream iofile("stud.dat", ios::in|ios::out|ios::binary); if(!iofile) { cerr<<"open error!"<<endl; abort( ); } cout<<"(1)向磁盘文件输入5个学生的数据并显示出来"<<endl; int i; for(i=0; i<5; i ) { iofile.write((char *)&stud[i],sizeof(stud[i])); stud[i].show(); } cout<<"(2)将磁盘文件中的第1,3,5个学生数据读入程序,并显示出来"<<endl; Student stud1[5]; for(i=0; i<5; i=i 2) { iofile.seekg(i*sizeof(stud[i]),ios::beg); iofile.read((char *)&stud1[i/2],sizeof(stud1[0])); stud1[i/2].show();; } cout<<endl; cout<<"(3)将第3个学生的数据修改后存回磁盘文件中的原有位置"<<endl; stud[2].setNum(1012); stud[2].setName("Wu"); stud[2].setScore(60); iofile.seekp(2*sizeof(stud[0]),ios::beg); iofile.write((char *)&stud[2],sizeof(stud[2])); iofile.seekg(0,ios::beg); cout<<"(4)从磁盘文件读入修改后的5个学生的数据并显示出来"<<endl; for(i=0; i<5; i ) { iofile.read((char *)&stud[i],sizeof(stud[i])); stud[i].show(); } iofile.close( ); getchar(); return 0; } /* (1)向磁盘文件输入5个学生的数据并显示出来 1001 Li 85 1002 Fun 97.5 1004 Wang 54 1006 Tan 76.5 1010 ling 96 (2)将磁盘文件中的第1,3,5个学生数据读入程序,并显示出来 1001 Li 85 1004 Wang 54 1010 ling 96 (3)将第3个学生的数据修改后存回磁盘文件中的原有位置 (4)从磁盘文件读入修改后的5个学生的数据并显示出来 1001 Li 85 1002 Fun 97.5 1012 Wu 60 1006 Tan 76.5 1010 ling 96 */

-End-

,