汽车网站建设公司哪家好深圳拼团手机网站开发
- 作者: 五速梦信息网
- 时间: 2026年03月21日 09:57
当前位置: 首页 > news >正文
汽车网站建设公司哪家好,深圳拼团手机网站开发,好看简单易做的网站,30天网站建设 视频教程指针详解
这段时间在看 Linux内核#xff0c;深觉C语言功底不扎实#xff0c;很多代码都看不太懂#xff0c;深入学习巩固C语言的知识很有必要。先从指针开始。
什么是指针
C语言里#xff0c;变量存放在内存中#xff0c;而内存其实就是一组有序字节组成的数组深觉C语言功底不扎实很多代码都看不太懂深入学习巩固C语言的知识很有必要。先从指针开始。
什么是指针
C语言里变量存放在内存中而内存其实就是一组有序字节组成的数组每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里数据对象是指存储在内存中的一个指定数据类型的数值或字符串它们都有一个自己的地址而指针便是保存这个地址的变量。也就是说指针是一种保存变量地址的变量。
前面已经提到内存其实就是一组有序字节组成的数组数组中每个字节大大小固定都是 8bit。对这些连续的字节从0开始进行编号每个字节都有唯一的一个编号这个编号就是内存地址。示意如下图 这是一个 4GB 的内存可以存放 2^32 个字节的数据。左侧的连续的十六进制编号就是内存地址每个内存地址对应一个字节的内存空间。而指针变量保存的就是这个编号也即内存地址。
为什么要使用指针
在C语言中指针的使用非常广泛因为使用指针往往可以生成更高效、更紧凑的代码。总的来说使用指针有如下好处
1指针的使用使得不同区域的代码可以轻易的共享内存数据这样可以使程序更为快速高效
2C语言中一些复杂的数据结构往往需要使用指针来构建如链表、二叉树等
3C语言是传值调用而有些操作传值调用是无法完成的如通过被调函数修改调用函数的对象但是这种操作可以由指针来完成而且并不违背传值调用。
如何声明一个指针
声明并初始化一个指针
指针其实就是一个变量指针的声明方式与一般的变量声明方式没太大区别
int *p; // 声明一个 int 类型的指针 p
char *p // 声明一个 char 类型的指针 p
int *arr[10] // 声明一个指针数组该数组有10个元素其中每个元素都是一个指向 int 类型对象的指针
int (arr)[10] // 声明一个数组指针该指针指向一个 int 类型的一维数组
int **p; // 声明一个指针 p 该指针指向一个 int 类型的指针 指针的声明比普通变量的声明多了一个一元运算符 “”。运算符 “*” 是间接寻址或者间接引用运算符。当它作用于指针时将访问指针所指向的对象。在上述的声明中p 是一个指针保存着一个地址该地址指向内存中的一个变量 p 则会访问这个地址所指向的变量。
声明一个指针变量并不会自动分配任何内存。在对指针进行间接访问之前指针必须进行初始化或是使他指向现有的内存或者给他动态分配内存否则我们并不知道指针指向哪儿这将是一个很严重的问题稍后会讨论这个问题。初始化操作如下
/ 方法1使指针指向现有的内存 */
int x 1;
int p x; // 指针 p 被初始化指向变量 x 其中取地址符 用于产生操作数内存地址/ 方法2动态分配内存给指针 */
int *p;
p (int *)malloc(sizeof(int) * 10); // malloc 函数用于动态分配内存
free(p);
// free 函数用于释放一块已经分配的内存常与 malloc 函数一起使用要使用这两个函数需要头文件 stdlib.h 指针的初始化实际上就是给指针一个合法的地址让程序能够清楚地知道指针指向哪儿。
未初始化和非法的指针
如果一个指针没有被初始化那么程序就不知道它指向哪里。它可能指向一个非法地址这时程序会报错在Linux上错误类型是Segmentation faultcore dumped提醒我们段违例或内存错误。它也可能指向一个合法地址实际上这种情况更严重你的程序或许能正常运行但是这个没有被初始化的指针所指向的那个位置的值将会被修改而你并无意去修改它。用一个例子简单的演示一下
#include stdio.hint main(){int *p;*p 1;printf(%d\n,*p);return 0;
} 这个程序可以编译通过但是运行的话会报错报错信息如下
[roothly_centos learn]# gcc -o point point.c
[roothly_centos learn]# ./point
Segmentation fault
[roothly_centos learn]# 要想使这个程序运行起来需要先对指针p进行初始化
#include stdio.hint main(){int x 1; int *p x;printf(%d\n,*p);*p 2;printf(%d\n,*p);return 0;
} 这段代码的输出结果如下
[roothly_centos learn]# gcc -o point point.c
[roothly_centos learn]# ./point
1
2 可以看到对指针进行初始化后便可以正常对指针进行赋值了。
NULL指针
NULL 指针是一个特殊的指针变量表示不指向任何东西。可以通过给一个指针赋一个零值来生成一个NULL指针。
#include stdio.hint main(){int p NULL;printf(p的地址为%d\n,p);return 0;
}/**************** 程序输出 p的地址为0
*************/ 可以看到指针指向内存地址0。在大多数的操作系统上程序不允许访问地址为0的内存因为该内存是为操作系统保留的。但是内存地址0有一个特别重要的意义它表明该指针指向一个不可访问的内存位置。
指针的运算
C 指针的算术运算只限于两种形式
指针/-整数
可以对指针变量p进行 p、p–、p i 等操作所得结果也是一个指针只是指针所指向的内存地址相比于p所指的内存地址前进或者后退了i个操作数。用一张图来说明一下 在上图中10000000等是内存地址的十六进制表示数值是假定的p 是一个int类型的指针指向内存地址 0x10000008 处。则p将指向与p相邻的下一个内存地址由于int型数据占4个字节因此p所指的内存地址为 1000000b。其余类推。不过要注意的是这种运算并不会改变指针变量p自身的地址只是改变了它所指向的地址。
指针-指针
只有当两个指针都指向同一个数组中的元素时才允许从一个指针减去另一个指针。两个指针相减的结果的类型是 ptrdiff_t它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离以数组元素的长度为单位而不是以字节为单位因为减法运算的结果将除以数组元素类型的长度。举个例子
#include stdio.hint main(){int a[10] {1,2,3,4,5,6,7,8,9,0};int sub;int *p1 a[2];int *p2 a[8];sub p2-p1; printf(%d\n,sub); // 输出结果为 6return 0;
}指针与数组
在C语言中指针与数组之间的关系十分密切。实际上许多可以用数组完成的工作都可以使用指针来完成。一般来说用指针编写的程序比用数组编写的程序执行速度快但另一方面用指针实现的程序理解起来稍微困难一些。
指针与数组的关系
我们先声明一个数组
int a[10]; // 声明一个int类型的数组这个数组有10个元素 我们可以用 a[0]、a[1]、…、a[9] 来表示这个数组中的10个元素这10个元素是存储在一段连续相邻的内存区域中的。
接下来我们再声明一个指针
int p; // 声明一个int类型的指针变量 p 是一个指针变量指向内存中的一个区域。如果我们对指针p做如下的初始化
p a[0]; // 对指针进行初始化p将指向数组 a 的第 1 个元素 a[0] 我们知道对指针进行自增操作会让指针指向与当前元素相邻的下一个元素即(p 1)将指向 a[1] 同样的*(p i)将指向a[i]。因此我们可以使用该指针来遍历数组a[10] 的所有元素。可以看到数组下标与指针运算之间的关系是一一对应的。而根据定义数组类型的变量或表达式的值是该数组第1个元素的地址且数组名所代表的的就是该数组第1个元素的地址故上述赋值语句可以直接写成
p a; // a 为数组名代表该数组最开始的一个元素的地址 很显然一个通过数组和下标实现的表达式可以等价地通过指针及其偏移量来实现这就是数组和指针的互通之处。但有一点要明确的是数组和指针并不是完全等价指针是一个变量而数组名不是变量它数组中第1个元素的地址数组可以看做是一个用于保存变量的容器。**更直接的方法我们可以直接看二者的地址并不一样
#include stdio.h
int main(){int x[10] {1,2,3,4,5,6,7,8,9,0};int p x;printf(x的地址为%p\n,x);printf(x[0]的地址为%p\n,x[0]);printf(p的地址为%p\n,p); // 打印指针 p 的地址并不是指针所指向的地方的地址p 2;printf((p2)的值为%d\n,p); // 输出结果为 3(p2)指向了 x[2]return 0;
} 结果如下
[roothly_centos learn]# gcc -o point point.c
[roothly_centos learn]# ./point
x的地址为0x7ffe02b98a60
x[0]的地址为0x7ffe02b98a60
p的地址为0x7ffe02b98a58
*(p2)的值为3 可以看到x的值与x[0]的地址是一样的也就是说数组名即为数组中第1个元素的地址。实际上打印x后发现x 的地址也是这个值。而x的地址与指针变量p的地址是不一样的。故而数组和指针并不能完全等价。
笔者注上述输出结果是在 centos7 64bit 的环境下使用 gcc 编译器得到的可以看到地址是一个12位的十六进制数转换成二进制是48位也就是说寻址空间有 256TB但是笔者的电脑只有 8GB 内存猜测是不是由于 linux 系统开启了内存分页机制这里寻址的是虚拟地址另外在Windows下使用 vs2015 编译运行的话则输出结果是一个 8位的十六进制数也就是32位二进制寻址空间为 4GB
指针数组
指针是一个变量而数组是用于存储变量的容器因此指针也可以像其他变量一样存储在数组中也就是指针数组。 指针数组是一个数组数组中的每一个元素都是指针。声明一个指针数组的方法如下
int *p[10]; // 声明一个指针数组该数组有10个元素其中每个元素都是一个指向int类型的指针 在上述声明中由于[]的优先级比*高故p先与[]结合成为一个数组 p[]再由int *指明这是一个int类型的指针数组数组中的元素都是int类型的指针。数组的第i个元素是 *p[i]而 p[i] 是一个指针。由于指针数组中存放着多个指针操作灵活在一些需要操作大量数据的程序中使用可以使程序更灵活快速。
数组指针
数组指针是一个指针它指向一个数组。声明一个数组指针的方法如下
int (*p)[10]; // 声明一个数组指针 p 该指针指向一个数组 由于()的优先级最高所以p是一个指针指向一个int类型的一维数组这个一维数组的长度是 10这也是指针p 的步长。也就是说执行p1时p 要跨过1个int 型数据的长度。数组指针与二维数组联系密切可以用数组指针来指向一个二维数组如下 #include stdio.hint main(){int arr[2][3] {1,2,3,4,5,6}; // 定义一个二维数组并初始化int (*p)[3]; // 定义一个数组指针指针指向一个含有3个元素的一维数组p arr; // 将二维数组的首地址赋给 p此时 p 指向 arr[0] 或 arr[0][0]printf(%d\n,(*p)[0]); // 输出结果为 1p; // 对 p 进行算术运算此时 p 将指向二维数组的下一行的首地址即 arr[1][0]printf(%d\n,(*p)[1]); // 输出结果为5return 0; } 指针与结构
简单介绍一下结构
结构是一个或多个变量的集合这些变量可能为不同的类型为了处理的方便而将这些变量组织在一个名字之下。由于结构将一组相关的变量看做一个单元而不是各自独立的实体因此结构有助于组织复杂的数据特别是在大型的程序中。声明一个结构的方式如下
struct message{ // 声明一个结构 messagechar name[10]; // 成员int age;int score;
};typedef struct message s_message; // 类型定义符 typedefs_message mess {tongye,23,83}; // 声明一个 struct message 类型的变量 mess,并对其进行初始化
/* 另一种更简便的声明方法 */
typedef struct{char name[10];int age;int score;
}message; 可以使用 结构名.成员 的方式来访问结构中的成员如下
#include stdio.hint main(){printf(%s\n,mess.name); // 输出结果tongyeprintf(%d\n,mess.age); // 输出结果23return 0;
}结构指针
结构指针是指向结构的指针以上面的结构为例可以这样定义一个结构指针
s_message *p; // 声明一个结构指针 p 该指针指向一个 s_message 类型的结构
p mess; // 对结构指针的初始化与普通指针一样也是使用取地址符 C语言中使用-操作符来访问结构指针的成员举个例子
#include stdio.htypedef struct{char name[10];int age;int score;
}message;int main(){message mess {tongye,23,83};message *p mess;printf(%s\n,p-mess); // 输出结果为tongyeprintf(%d\n,p-score); // 输出结果为83return 0;
} 指针与函数
C语言的所有参数均是以“传值调用”的方式进行传递的这意味着函数将获得参数值的一份拷贝。这样函数可以放心修改这个拷贝值而不必担心会修改调用程序实际传递给它的参数。
指针作为函数的参数
传值调用的好处是是被调函数不会改变调用函数传过来的值可以放心修改。但是有时候需要被调函数回传一个值给调用函数这样的话传值调用就无法做到。为了解决这个问题可以使用传指针调用。指针参数使得被调函数能够访问和修改主调函数中对象的值。用一个例子来说明
#include stdio.hvoid swap1(int a,int b) // 参数为普通的 int 变量
{int temp;temp a;a b;b temp;
}void swap2(int *a,int *b) // 参数为指针接受调用函数传递过来的变量地址作为参数对所指地址处的内容进行操作
{int temp; // 最终结果是地址本身并没有改变但是这一地址所对应的内存段中的内容发生了变化即x,y的值发生了变化temp *a;*a *b;*b temp;
}int main()
{int x 1,y 2;swap1(x,y); // 将 x,y 的值本身作为参数传递给了被调函数printf(%d %5d\n,x,y); // 输出结果为1 2swap(x,y); // 将 x,y 的地址作为参数传递给了被调函数传递过去的也是一个值与传值调用不冲突printf(%d %5d\n,x,y); // 输出结果为2 1return 0;
}指针函数
指针函数 顾名思义它的本质是一个函数不过它的返回值是一个指针。其声明的形式如下所示
ret *func(args, …); 其中func是一个函数args是形参列表ret *作为一个整体是 func函数的返回值是一个指针的形式。 下面举一个具体的实例来做说明
文件pointer_func.c# include stdio.h
include stdlib.hint * func_sum(int n)
{if (n 0){printf(error:n must be 0\n);exit(-1);}static int sum 0;int *p sum;for (int i 0; i n; i){sum i;}return p; }int main(void) {int num 0;printf(please input one number:);scanf(%d, num);int *p func_sum(num); printf(sum:%d\n, *p);return 0; } 上例就是一个指针函数的例子其中int * func_sum(int n)就是一个指针函数 其功能十分简单是根据传入的参数n来计算从0到n的所有自然数的和其结果通过指针的形式返回给调用方。 以上代码的运行结果如下所示 [roothly_centos learn]# gcc -o point_func point_func.c [roothly_centos learn]# ./point_func please input one number:100 sum:4950 如果上述代码使用普通的局部变量来实现也是可以的如下所示 文件pointer_func2.c# include stdio.h
include stdlib.hint func_sum2(int n)
{ if (n 0){ printf(error:n must be 0\n);exit(-1);}int sum 0;int i 0;for (i 0; i n; i){ sum i;}return sum; }int main(void) {int num 0;printf(please input one number:);scanf(%d, num);int ret func_sum2(num);printf(sum2:%d\n, ret);return 0; } 本案例中func_sum2函数的功能与指针函数所实现的功能完全一样。 [roothly_centos learn]# gcc -o point_func point_func.c [roothly_centos learn]# ./point_func please input one number:100 sum:4950 不过在使用指针函数时需要注意一点相信细心地读者已经发现了对比func_sum和func_sum2函数除了返回值不一样之外还有一个不同的地方在于在func_sum中变量sum使用的是静态局部变量而func_sum2函数中变量sum使用的则是普通的变量。 如果我们把指针函数的sum定义为普通的局部变量会是什么结果呢不妨来试验一下 文件pointer_func3.c# include stdio.h
include stdlib.hint * func_sum(int n)
{if (n 0){printf(error:n must be 0\n);exit(-1);}int sum 0;int *p sum;for (int i 0; i n; i){sum i;}return p; }int main(void) {int num 0;printf(please input one number:);scanf(%d, num);int *p func_sum(num); printf(sum:%d\n, *p);return 0; } 执行以上程序发现仍然能得到正确的结果 [roothly_centos learn]# gcc -o point_func point_func.c [roothly_centos learn]# ./point_func please input one number:100 sum:4950 可如果我们把main函数里面稍微改动一下 int main(void) {int num 0;printf(please input one number:);scanf(%d, num);int *p func_sum(num);printf(wait for a while…\n); //此处加一句打印printf(sum:%d\n, *p);return 0; } 我们在输出sum之前打印一句话这时看到得到的结果完全不是我们预先想象的样子得到的并不是我们想要的答案。 [roothly_centos learn]# gcc -o point_func point_func.c [roothly_centos learn]# ./point_func please input one number:100 wait for a while… sum:0 为什么会出现上面的结果呢 其实原因在于一般的局部变量是存放于栈区的当函数结束栈区的变量就会释放掉如果我们在函数内部定义一个变量在使用一个指针去指向这个变量当函数调用结束时这个变量的空间就已经被释放这时就算返回了该地址的指针也不一定会得到正确的值。上面的示例中在返回该指针后立即访问的确是得到了正确的结果但这只是十分巧合的情况如果我们等待一会儿再去访问该地址很有可能该地址已经被其他的变量所占用这时候得到的就不是我们想要的结果。甚至更严重的是如果因此访问到了不可访问的内容很有可能造成段错误等程序崩溃的情况。 因此在使用指针函数的时候一定要避免出现返回局部变量指针的情况。 那么为什么用了static就可以避免这个问题呢 原因是一旦使用了static去修饰变量那么该变量就变成了静态变量。而静态变量是存放在数据段的它的生命周期存在于整个程序运行期间只要程序没有结束该变量就会一直存在所以该指针就能一直访问到该变量。 因此还有一种解决方案是使用全局变量因为全局变量也是放在数据段的但是并不推荐使用全局变量。 函数指针 与指针函数不同函数指针的本质是一个指针该指针的地址指向了一个函数所以它是指向函数的指针。 我们知道函数的定义是存在于代码段因此每个函数在代码段中也有着自己的入口地址函数指针就是指向代码段中函数入口地址的指针。 其声明形式如下所示 ret (*p)(args, …); 其中ret为返回值*p作为一个整体代表的是指向该函数的指针args为形参列表。其中p被称为函数指针变量 。 关于函数指针的初始化与数组类似在数组中数组名即代表着该数组的首地址函数也是一样函数名即是该数组的入口地址因此函数名就是该函数的函数指针。 因此我们可以采用如下的初始化方式 函数指针变量 函数名; 下面还是以一个简单的例子来具体说明一下函数指针的应用 文件func_pointer.c#include stdio.hint max(int a, int b) {return a b ? a : b; }int main(void) {int (*p)(int, int); //函数指针的定义//int (*p)(); //函数指针的另一种定义方式不过不建议使用//int (*p)(int a, int b); //也可以使用这种方式定义函数指针p max; //函数指针初始化int ret p(10, 15); //函数指针的调用//int ret (*max)(10,15);//int ret (*p)(10,15);//以上两种写法与第一种写法是等价的不过建议使用第一种方式printf(max %d \n, ret);return 0; } 上面这个函数的功能也十分简单就是求两个数中较大的一个数。值得注意的是通过函数指针调用的方式。 首先代码里提供了3种函数指针定义的方式这三种方式都是正确的比较推荐第一种和第三种定义方式。然后对函数指针进行初始化前面已经提到过了直接将函数名赋值给函数指针变量名即可。 上述代码运行的结果如下 [roothly_centos learn]# gcc -o func_point func_point.c [roothly_centos learn]# ./func_point max 15 调用的时候既可以直接使用函数指针调用也可以通过函数指针所指向的值去调用。(*p)所代表的就是函数指针所指向的值也就是函数本身这样调用自然不会有问题。 为什么要使用函数指针 那么有不少人就觉得本来很简单的函数调用搞那么复杂干什么其实在这样比较简单的代码实现中不容易看出来当项目比较大代码变得复杂了以后函数指针就体现出了其优越性。 举个例子如果我们要实现数组的排序我们知道常用的数组排序方法有很多种比如快排插入排序冒泡排序选择排序等如果不管内部实现你会发现除了函数名不一样之外返回值包括函数入参都是相同的这时候如果要调用不同的排序方法就可以使用指针函数来实现我们只需要修改函数指针初始化的地方而不需要去修改每个调用的地方特别是当调用特别频繁的时候。 回调函数 函数指针的一个非常典型的应用就是回调函数。 什么是回调函数 回调函数就是一个通过指针函数调用的函数。其将函数指针作为一个参数传递给另一个函数。 回调函数并不是由实现方直接调用而是在特定的事件或条件发生时由另外一方来调用的。同样我们来看一个回调函数的例子 文件callback.c#includestdio.h #includestdlib.h//函数功能实现累加求和 int func_sum(int n) {int sum 0;if (n 0){printf(n must be 0\n);exit(-1);}for (int i 0; i n; i){sum i;}return sum; }//这个函数是回调函数其中第二个参数为一个函数指针通过该函数指针来调用求和函数并把结果返回给主调函数 int callback(int n, int (*p)(int)) {return p(n); }int main(void) {int n 0;printf(please input number:);scanf(%d, n);printf(the sum from 0 to %d is %d\n, n, callback(n, func_sum)); //此处直接调用回调函数而不是直接调用func_sum函数return 0; } 上面这个简单的demo就是一个比较典型的回调函数的例子。在这个程序中回调函数callback无需关心func_sum是怎么实现的只需要去调用即可。 这样的好处就是如果以后对求和函数有优化比如新写了个func_sum2函数的实现我们只需要在调用回调函数的地方将函数指针指向func_sum2即可而无需去修改callback函数内部。 以上代码的输出结果如下 [roothly_centos learn]# gcc -o call_back call_back.c [roothly_centos learn]# ./call_back please input number:10 the sum from 0 to 10 is 45 回调函数广泛用于开发场景中比如信号函数、线程函数等都使用到了回调函数的知识。
- 上一篇: 汽车网站建设的基本功能营销推广措施有哪些
- 下一篇: 汽车网站建设论文建模培训机构有哪些
相关文章
-
汽车网站建设的基本功能营销推广措施有哪些
汽车网站建设的基本功能营销推广措施有哪些
- 技术栈
- 2026年03月21日
-
汽车网站代码太原网络搭建
汽车网站代码太原网络搭建
- 技术栈
- 2026年03月21日
-
汽车网站大全汽车网网站不备案 能打开吗
汽车网站大全汽车网网站不备案 能打开吗
- 技术栈
- 2026年03月21日
-
汽车网站建设论文建模培训机构有哪些
汽车网站建设论文建模培训机构有哪些
- 技术栈
- 2026年03月21日
-
汽车网站建设论文女生学电子商务好吗
汽车网站建设论文女生学电子商务好吗
- 技术栈
- 2026年03月21日
-
汽车网站建设网新网站建设流程图
汽车网站建设网新网站建设流程图
- 技术栈
- 2026年03月21日






