指针是C语言最重要也是最难理解的部分,它在我们平时的工作中无处不在。
今天我们继续来看看指针的剩下的知识总结吧!上一批的话可以在主页看到哦~
5 指针与结构体
一个指针,它指向的可以是一个结构体类型,这称为结构体指针。而一个结构体,它的成员中也可以有指针成员。
struct{ char *name; //姓名 int num; //学号 int age; //年龄 char group; //所在小组 float score; //成绩} stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;
上面的代码中,定义了一个结构体变量stu1。这个变量中有一个指针变量name。还定义了一个结构体指针pstu。
我们想要通过结构体指针访问结构体成员一般有以下两种方式:
(*pstu).name;pstu->name;
6 指针与const:常量指针与指针常量
初学者常常对这两个概念搞错。首先,我认为需要理解这里说的常量是什么意思。常量就是只可读不可修改的。那常量指针和指针常量到底哪个是只可读不可修改的呢?是指针还是指针指向的内容?这里有一个方法,能让你迅速明白哪个是不可修改的。就是在声明时,以星号(*)为界,分成两部分,星号左边的和星号右边的。const在哪边,那个就是只可读不可修改的。以下面这个代码为例,我们来分析一下:以星号(*)为界,星号左边是char,没有const关键词,所以它指向的内容不是常量。然后,我们看星号的右边是const ptr,所以我们可以说ptr是一个常量。所以,这行代码声明了一个是常量的指针但是指向的内容不是常量。即这个是一个指针常量。
char* const ptr = "just a string";
类似的,我们也可以分析下面的代码:
// Neither the data nor the pointer are const//char* ptr = "just a string"; // Constant data, non-constant pointer//const char* ptr = "just a string"; // Constant pointer, non-constant data//char* const ptr = "just a string"; // Constant pointer, constant data//const char* const ptr = "just a string";
6.1 指针常量(Constant Pointers)
指针常量(Constant Pointers): 它的本质是一个常量,只不过这个常量是指针。由于指针是只可读不可修改的,所以这个指针不能指向别的地址了,但是该地址里的内容还是可以改变的。指针常量的声明格式如下:
* const 例如: int * const ptr;
我们来看下序:
#includeint main(void){ int var1 = 0, var2 = 0; int *const ptr = &var1; ptr = &var2; printf("%d ", *ptr); return 0;}
上面这段程序中:
我们首先定义了两个变量var1,var2;
然后,定义了一个指针常量ptr,并且指向了var1
接着,试图让ptr指向var2
最后,打印出指针ptr指向的地址的内容
让我们来运行一下这个程序:
main.c: In function 'main':main.c9: error: assignment of read-only variable 'ptr' ptr = &var2; ^
我们看到这个程序编译报错了:试图对只读(read-only)变量ptr进行赋值。所以,一旦我们定义了指针常量,那这个指针就不能指向其他变量了。
但是我们还是可以修改指向的地址里的内容的:
#includeint main(void){ int var1 = 0; int *const ptr = &var1; *ptr = 10; // OK printf("%d ", *ptr); // 10 return 0;}
6.2 常量指针(Pointer to Constants)
常量指针(Pointer to Constants):它的本质是一个指针,只不过它指向的值是常量(只可读,不可修改)。由于指向的是一个只可读不修改的值,所以指针不能通过它存储的地址间接修改这个地址的值,但是这个指针可以指向别的变量。
常量指针的声明格式如下:
const* 例如: const int* ptr;
还是有一段程序:
#includeint main(void){ int var1 = 0; const int* ptr = &var1; *ptr = 1; printf("%d ", *ptr); return 0;}
我们还是来分析一下这个程序:
我们定义了一个变量 var1,并且初始化为0
然后我们定义了一个指针常量ptr,并且将它指向了var1
接着,试图通过指针ptr来改变var1的值
最后,打印出ptr指向的地址的内容。
我们进行编译:
main.c: In function 'main':main.c10: error: assignment of read-only location '*ptr' *ptr = 1; ^
编译报错也很明显: *ptr是一个只读的。所以不能通过ptr来修改var1的值。
但是,我们可以将ptr指向其他的变量:
#includeint main(void){ int var1 = 0; const int* ptr = &var1; printf("%d ", *ptr); // 0 int var2 = 20; ptr = &var2; // OK printf("%d ", *ptr); // 20 return 0;}
6.3 指向常量的常量指针
理解了上面两种类型的话,理解这个就很容易了。指向常量的常量指针是指这个指针既不能指向其他的地址也不能通过地址修改内容。
它的声明格式如下:
const* const 例如: const int* const ptr;
同样,下面一段程序,我想你一定知道哪里编译错误了。
#includeint main(void){ int var1 = 0,var2 = 0; const int* const ptr = &var1; *ptr = 1; ptr = &var2; printf("%d ", *ptr); return 0;}
编译结果:
main.c: In function 'main':main.c10: error: assignment of read-only location '*ptr' *ptr = 1; ^main.c9: error: assignment of read-only variable 'ptr' ptr = &var2; ^
7 指针与函数
7.1 函数指针
指针与函数相结合有两种情况:指针函数、函数指针。
指针函数,它的本质是一个函数,它的返回值是一个指针。
int * func(int x, int y);
函数名本身就是一个指针(地址),这个地址就是函数的入口地址。
#includeint sum(int a, int b){ return a + b;} int main(){ printf("%p ", sum); return 0;}
输出:
0000000000401550
而函数指针,它的本质是一个指针。只不过它存的地址恰好是一个函数的地址罢了。
函数指针变量定义的格式一般是:
返回值 (*变量名)(参数列表)
比如:
#includeint sum(int a, int b){ return a + b;} int main(){ printf("%p ", sum); int (*psum)(int, int); // 函数指针变量,参数名可以省略 psum = sum; printf("%p ", psum); return 0;}
输出:
00000000004015500000000000401550
可以发现,两者地址相等。
函数指针类型的定义:
typedef 返回值 (* 类型名)(参数列表);复制代码
比如:
typedef int(*PSUM)(int, int);PSUM pSum2 = sum;PSUM pSum3 = sum;
这样的好处就是,首先通过typedef定义一个函数指针类型PSUM,定义完后,PSUM就相当于一种新的类型,可以用此类型去定义其他函数指针变量,就不用每次都使用int(*pSum)(int, int);来定义一个函数指针变量。
#includeint sum(int a, int b){ return a + b;}int func2(int a, int b){ return a - b;}typedef int (*PFUNC) (int, int);int main(){ int (*psum)(int, int); psum = sum; printf("psum(4, 5):%d ", psum(4, 5)); PFUNC p2 = func2; printf("p2(5, 2):%d ", p2(5, 2)); p2 = sum; printf("p2(5, 2):%d ", p2(5, 2)); return 0;}
输出:
psum(4, 5):9p2(5, 2):3p2(5, 2):7
7.2 回调函数
说到函数指针,那还有一个概念不得不提——回调函数。因为在实际的项目代码中实在是太常见了。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
那为什么要使用回调函数呢?或者说使用回调函数有什么好处呢?回调函数允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似的事情时,可以灵活的使用不同的方法。
怎么使用回调函数:
#includeint Callback_1(int a) ///< 回调函数1{ printf("Hello, this is Callback_1: a = %d ", a); return 0;} int Callback_2(int b) ///< 回调函数2{ printf("Hello, this is Callback_2: b = %d ", b); return 0;} int Callback_3(int c) ///< 回调函数3{ printf("Hello, this is Callback_3: c = %d ", c); return 0;} int Handle(int x, int (*Callback)(int)) // 注意这里用到的函数指针定义{ Callback(x);} int main(){ Handle(4, Callback_1); Handle(5, Callback_2); Handle(6, Callback_3); return 0;}
如上述代码:可以看到,Handle()函数里面的参数是一个指针,在main()函数里调用Handle()函数的时候,给它传入了函数Callback_1()/Callback_2()/Callback_3()的函数名,这时候的函数名就是对应函数的指针,也就是说,回调函数其实就是函数指针的一种用法。
8 二维指针
二维指针,或者二级指针。就是指向指针的指针。比如:
#includeint main(){ int a = 10; int *pa = &a; int **ppa = &pa; printf("%p, %p, %p, %p, %p", a, pa, *pa, ppa, *ppa); return 0;}
输出如下:
000000000000000A, 000000000061FE14, 000000000000000A, 000000000061FE08, 000000000061FE14
从输出结果也可以看到,pa存的内容*pa= 000000000000000A,刚好与a的地址相同。而ppa存的内容*ppa= 000000000061FE14也刚好等于pa的地址。它们之间的内存关系可以用如下的图表示:
8.1 命令行参数
处理命令行参数是指向指针的指针的一个用武之地。
一般main函数具有两个形参。第一个通常称为argc,它表示命令行参数的数目。第2个通常称为argv,它指向一组参数值。由于参数的数目并没有内在的限制,所以argv指向这组参数值(从本质上来说是一个数组)的第一个元素。这些元素的每个都是指向一个参数文本的指针。如果程序需要访问命令行参数,main函数在声明时就要加上这些参数。
【int main(int argc, char **argv)
举例:
#includeint main(int argc, char *argv[]){ printf("argc: %d ", argc); // 打印参数,直到遇到NULL指针。程序名被跳过 while (*++argv != NULL) { printf("%s ", *argv); } return 0;}
在windows上执行: est2.exe hello world
argc: 3helloworld
注意,如果命令行中传递的一个参数包括空格,就需要用 ""将参数括起来,比如:
. est2.exe "hello word"
则上面的代码将输出:
argc: 2hello word
9 结束语
本文关于指针的讲解就结束了。我相信你一定对指针有更深入的了解。
全部0条评论
快来发表一下你的评论吧 !