万字长篇————C语言指针学习汇总
经过一段时间的学习,我们已经接触到了C语言的很多知识了。不过目前我们接下来我们要接触C语言中一个最大的“门槛”:指针。
什么是指针?
在介绍指针之前,我们首先要明白变量与地址之间的关系。
举一个生活中的案例:一个宿舍楼内有一百个房间,你就身处其中一个房间内,你的朋友想来找你玩,但是这么多房间一个一个找很麻烦,效率很低。但是如果我们把每个房间编上号码,如101、102……,这样就方便你的朋友找到你了。
而对应到C语言中,内存空间就是这个“宿舍楼”,而想要对内存进行高效的管理就需要“房间”来将内存划分为一个又一个的内存单元,就如同学生宿舍一样,每个内存单元的大小对应一个字节,有8个比特位。一个比特位只能存放一个1或0
内存的储存关系对应关系如上。
每个内存单元都有一个编号(相当于宿舍门牌号),有了这个编号就可以快速找到一个内存单元。生活中我们将门牌号称之为地址,C语言中我们也将内存单元对应的编号称之为地址,而在C语言中这个地址有一个新的名字:指针。因此有内存单元的编号==地址==指针
接下来我们该如何理解编地呢?
前面我们讲过变量创建的本质就是向内存申请空间,就会有属于自己的地址,而&符号就是取出变量所在地址的符号。
如图所示的代码中,变量p与&a实际上是等价的,因此可以称p为指针变量,而int *是p的类型。我们可以得出以下结论
(1)指针就是地址
(2)指针变量是变量,是专门用来存放地址的变量
(3)存放在指针变量中的值,我们认为是地址。
指针的变量的意义
指针的解引用
如下图的代码中,指针变量为怕,而*p就是通过p所在的地址,找到指向的内存空间。实际上*p就是a。
指针的大小
如下图的代码,打印出来的就是分别在X64(64位环境)和X86(32位环境)下指针变量的长度。
在X64环境下的结果
在X86环境下的结果
int 是4个字节、char是1个字节,但是由上图可知,在相同环境下int *与char *的大小相同。
由上图可知,指针变量的长度与类型是无关的。在64位环境下指针变量长度为8;在32位环境下指针变量长度为4。
既然如此,那不同类型的指针意义是什么呢?实际上指针的类型象征着对取地址权限的差异。如int * 可以取4个字节的地址,char * 只可以取出1个字节的地址,就像下面这张图一样。
指针+-整数
指针的类型决定了指针向后访问的“距离”有多大。
以上为代码展示,以下为原理概述。
void * 类型指针
我们知道指针变量都有对应的类型,应用时必须使用对应的类型,使用错误的类型编译器就会报错,但是当对方向我们传递了一个未知类型的指针时我们应该怎么办呢?这时我们就需要用到void *这种特殊类型的指针。void*类型的指针没有具体的类型,又被称之为泛指针,可以用来接受任意类型的指针。但是void * 的指针无法应用于解引用和加减饭。
void * 类型的指针通常应用于函数参数的部分,用来接收不同类型的指针,实现泛性编程的效果,使得一个函数可以处理多种数据。
指针的运算
指针与整数
在之前的练习中,我们尝试过用正常循环的方式打印数组的元素,但是这一次我们将使用指针来打印数组的内容。
结果为:
因为数组的元素是连续存放的,因此当我们得知第一个元素的地址的时候就可以顺藤摸瓜的知道所有的内容了。
指针与指针
指针-指针 的前提:两个指针指向同一个内存空间
指针-指针的结果:两个指针之间的元素个数。
结果为:
指针-指针的应用
结果为:·
扩充内容:size_t的概念与使用
在C语言中,size_t
是一种无符号整数类型,专门用于表示对象的大小(如内存块大小、数组元素数量等)。以下是关于 size_t
的关键点:
1. 定义与头文件
定义位置:size_t
在标准库头文件 <stddef.h>
、<stdio.h>
、<stdlib.h>
等中通过 typedef
定义。
-
底层类型:具体类型由编译器和平台决定,通常是:
32位系统:unsigned int
(4字节,0 ~ 4,294,967,295) - 64位系统:
unsigned long
或unsigned long long
(8字节,0 ~ 18,446,744,073,709,551,615)
2. 为什么需要 size_t
?
-
size_t array_size = 10;
int arr[array_size];
for (size_t i = 0; i < array_size; i++) {
// 安全遍历
}内存安全的表示:size_t
的宽度足够大,可以表示系统中可能的最大对象(与指针大小一致,如sizeof(void*)
)。 -
与
sizeof
运算符兼容:sizeof
返回值的类型就是size_t
。 -
避免溢出:无符号特性确保非负值,适合内存相关的计算(如数组索引、内存分配)。
3. 典型使用场景
-
内存操作函数:如
malloc(n * sizeof(int))
的参数类型为size_t
。 -
字符串/数组长度:如
strlen()
返回size_t
。 -
循环计数器:遍历数组或容器时,使用
size_t
避免索引越界。 -
size_t array_size = 10;
int arr[array_size];
for (size_t i = 0; i < array_size; i++) {
// 安全遍历
}
size_t
是C语言中表示对象大小的无符号整数类型,具有平台无关性,确保内存相关操作的安全性。在涉及内存、数组、循环的场景中优先使用它,而非固定类型如 int
或 unsigned int
。
-
4. 格式化输出
-
格式说明符:使用
%zu
(z
表示size_t
,u
表示无符号)。 -
size_t size = 100;
printf("Size: %zu\n", size); // 正确
// 错误示例:printf("%d", size);(可能导致未定义行为) -
5. 注意事项
-
无符号回绕:当
size_t
减到 0 后再减 1 时,会回绕到最大值(需注意循环条件)。 -
与有符号类型的比较:混合使用可能引发警告或逻辑错误(建议显式转换)。
总结
size_t
是C语言中表示对象大小的无符号整数类型,具有平台无关性,确保内存相关操作的安全性。在涉及内存、数组、循环的场景中优先使用它,而非固定类型如 int
或 unsigned int
。
指针的关系运算
const修饰指针
const修饰变量
const表示常变量,即你无法改变的。在下图所示的代码中,a是无法直接改变的,在C语言中a被称之为常变量,意思就是a是变量但是无法被修改
但是可以通过以下的方式修改
结果为:
const修饰指针变量
const可以放在*左边和右边。其代表的含义各不相同。
一、const int *p
对于这种情况下,const限制的是*p,也就是说指针指向的内容无法通过改变p来修改了
二、int * const p
对于这种情况下,const限制的是变量p本身,p本身无法改变,但是p所指向的内容是可以通过p来改变的。
可以参考如下代码:
野指针
概念:指针指向的位置没有是不可知的(随机的、不正确的、没有限制的)。
每个程序运行后都会向内存申请空间程序中的指针,指向的内存不属于当前的程序,因此就会产生野指针。
野指针成因
一、指针没有初始化
二、指针越界访问
打印结果如下
当指针超出了数组的范围时候,会随机分配到内存的一个值
三、指针的空间被释放
如果你实在要返回n的指针,建议加上static修饰n
这样就能返回n的值了。
PS:这里做这项补充一下之前遗漏的知识点:static和extern
在C语言中,static
和extern
是两个用于控制变量或函数作用域和链接属性的关键字,它们的核心区别如下:
1. static
关键字
用途:
-
限制作用域:将变量或函数的作用域限定在当前文件或当前函数内。
-
改变存储周期:局部变量声明为
static
时,生命周期延长至程序运行结束。
应用场景:
1.静态局部变量(函数内部):
void counter() {
static int count = 0; // 仅在函数内可见,但生命周期全局
count++;
printf("Count: %d\n", count);
}
特点:
-
每次函数调用时保留上一次的值。
-
初始化仅执行一次
静态全局变量/函数(文件作用域):
static int hidden_var = 42; // 仅当前文件可见
static void internal_func() { ... } // 仅当前文件可调用
2. extern
关键字
用途:
-
声明外部符号:表示变量或函数定义在其他文件中。
-
扩展作用域:允许跨文件访问全局变量或函数。
应用场景:
-
引用其他文件的全局变量:
File1.c:int global_var = 100; // 定义全局变量 - File2.c:extern void public_func(); // 声明外部函数
3. 核心区别对比
特性 | static | extern |
---|---|---|
作用域控制 | 限制为当前文件或函数 | 扩展为跨文件访问 |
存储周期影响 | 可延长局部变量的生命周期 | 不影响存储周期 |
是否分配存储空间 | 定义时分配 | 声明时不分配,依赖外部定义 |
多文件项目中的作用 | 隐藏内部实现(信息隐藏) | 共享全局资源(接口暴露) |
默认链接属性 | 内部链接(仅当前文件) | 外部链接(跨文件可见) |
4. 综合示例
场景:多文件项目中控制符号的可见性
FileA.c
static int private_var = 10; // 仅FileA可见
int public_var = 20; // 其他文件可通过extern访问
static void private_func() { // 仅FileA可调用
// ...
}
void public_func() { // 其他文件可调用
// ...
}
FileB.c
extern int public_var; // 正确:引用FileA的public_var
extern void public_func(); // 正确:调用FileA的public_func
// extern int private_var; // 错误:无法访问FileA的static变量
// extern void private_func(); // 错误:无法调用FileA的static函数
5. 常见问题
问题1:static
全局变量能被其他文件访问吗?
-
不能。
static
全局变量仅在定义它的文件中可见,其他文件无法通过extern
声明访问。
问题2:extern
变量可以初始化吗?
-
不可以。
extern
仅用于声明,而非定义。初始化必须在定义时完成:
extern int var = 5; // 错误:extern声明不能初始化
int var = 5; // 正确:定义并初始化
问题3:函数默认是static
还是extern
?
-
默认情况下,函数具有外部链接(相当于
extern
),除非显式声明为static
。
总结
-
static
:用于隐藏和持久化,限制作用域或延长生命周期。 -
extern
:用于共享和扩展,实现跨文件访问全局资源。 -
关键设计原则:
-
使用
static
保护模块内部实现(封装)。 -
使用
extern
暴露模块公共接口。
-
如何规避野指针
一、指针初始化
如果明确知道指针的指向哪里就直接赋值地址,如果不知道指针指向哪里,就给指针赋值NULL。
NULL是C语言定义的一个标识常量,值为0,但是0也是一个地址。但是这个地址无法使用,读写该地址会报错。
如下所示的代码中,指针变量*p表示传递的是一个野指针。
二、小心指针越界
一个程序向内存申请了多少空间,通过指针就只能访问多少空间,不能超出范围,超出了就是非法访问。
三、避免返回局部变量的地址
参考成因三图一
四、当指针变量不再使用的时候,及时地将它们置NULL,指针使用前检查有效性
当指针变量只想一块区域的时候,我们通过指针可以访问该区域,后期不再使用该指针的时候,我们可以将该指针置为NULL。因为约定俗成的规则就是:只要是NULL指针就不去访问。同时使用前可以判断指针是否为NULL。
我们可以将指针视之为野狗,野狗放任不管的情况下十分危险的,所以我们可以找一棵树把野狗拴起来,,就相对安全了,给指针变量及时赋值为NULL就是类似于把野狗拴起来,也就是把野指针暂时管理起来。
不过野狗即使拴起来也很危险,所以我们要绕着走不要挑逗他。对于指针来说也是如此,在使用前,我们也要判断指针是否为NULL,看看是不是被拴起来的野狗。如果是我们不能使用,如果不时我们再去使用。
示范案例:
assert断言
<assert.h>头文件中定义了宏assert(),用于运行时确保程序符合指定条件,如果不符合终止程序,这个宏常常被称之为“断言”。
当程序运行到该段代码时,验证变量p是否等于NULL,如果不等于NULL则程序正常运行,否则程序会 终止运行并给出错误信息。
assert()宏需要接受一个表达式作为参数,如果表达式为真(返回值为非零),assert()不会产生任何作用,程序继续运行;如果该表达式为假(返回值为零),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式在内的文件名和行号。
assert()的使用对程序员是相当友好的,使用assert()有几个好处:它不仅可以自动表示文件和出问题的行号,还有一种无需更改代码就能换开启或者关闭的assert的机制,如果已经确认程序没有问题,就不需要再做判断,就在#include<assert.h>的语句前定义一个宏NDEBUG。
然后重新编译程序,编译器就会禁用文件中所有的assert()语句。如果程序又出现了问题,可以移除或者注释掉 #define NDEBUG 这条指令重新编译这样就可以重启assert()这条语句了。
缺点是:因为引入了额外的检查,增加了程序运行的时间。
一般我们旨在Debug中shiyong,zai 在Release版本中选择禁用assert,在VS这样的集成开发环境中,在Release版本中,直接就优化掉了。这样在Debug版本中不影响程序员问题,在Release版本不影响用户的效率。
指针的使用和传址调用
指针的传值和传址调用
指针的内容写到这里已经不少了,那么实际应用中有什么问题非要用指针不可呢?
举例:写一段代码实现两个数值之间的交换:
这个代码在上一期的操作符也写过几个,这里用指针在写一次。
算法一:要求有第三个变量
算法二:有溢出风险
算法三:按位异或算法
算法四:传址调用算法
在算法四中,当我们将exchange2函数注释后(即exchange1函数调用),结果为
这是为什么呢?
在exchange1 函数中,函数中定义的a、b与实际main()函数中的a、b并不一样。函数当中的a、b确实接受了main函数中的a、b的值,但是两组变量是独立的空间,无法互相影响。(如下所示)
exchange1函数使用时将变量本身传递给函数,这种调用方式称之为传值调用
结论:当实参传递给函数时形参会单独创立一个空间传递实参,对形参的修改是不影响实参的。
exchange2函数是将变量的地址直接传递给函数,称之为传址调用
传址调用可以让函数与主函数之间建立真正的联系,在函数内部修改主函数的变量;在未来函数中只是需要主函数中的变量值来进行计算的就可以采用传值调用。如果函数内部要修改主函数内变量的值的,就需要传址调用。
数组名的理解
数组名实际上就是地址,代码验证如下:
但是有两个例外
1.sizeof(数组名):单独放数组名这个数组出了表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名:这个数组名表示的整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
实验验证如下:
整个数组的地址和数组首元素的地址是有区别的,具体体现在如下情况
可知,指针类型决定了+1跳过了多少距离。
指针访问数组
算法二与上相同
本质上,p[i]等价于*(p+i)。
在此处,*(p+i)等价于*(arr+i)等价于arr[i]等价于i[arr]。
结果如下:(直截取一部分)
原理:[]只是一个下标引用操作符。
数组传参的本质
之前我们都是函数外进行数组元素个数的计算的,今天我们来试试将数组交给函数,让函数帮我拿计算数组的元素个数。
错误示范:
结果为:
这是为什么呢?
已知指针变量大小为8,则下列解析:
由以上可知,本质上数组传参传递的是首元素的地址。
因此实际上函数形参部分应该使用指针变量来接受首元素的地址。那么在函数内部我们用sizeof()计算的是一个地址的大小而不是数组的大小。正是因为函数参数的本质是地址,所以函数内部没有办法求数组元素个数。
冒泡排序(重点!!!)
冒泡排序:两两相邻的元素进行比较。
比如:9 8 7 6 5 4 3 2 1 0将其排成升序,即0 1 2 3 4 5 6 7 8 9
代码如下:
主函数和打印函数
冒泡排序函数:
结果:
但是如果当数组中已经有一部分排列有序时候,就会多点计算量,如:
所以我们可以做如下的优化:
这样效率就会更高。
二级指针
关于二级指针,可通过如下代码及其原理图理解。
类比于一级指针,我们可以知道,对*ppa解引用得到的是pa的地址。*ppa访问的是pa
同理,如上图可知,**ppa通过*pa找到pa,然后通过对pa的解引用找到a。因此**ppa访问的就是a
指针数组
指针数组本质上仍然是数组。类比于字符数组char arr[]是用来存放字符元素的可知,指针数组是用来存放指针类型的的元素的。
指针数组的每个元素是地址,又单独的指向一片区域。
但是注意,指针数组的元素必须为同一类型的指针
以下是指针数组代码的打印。
指针数组模拟二维数组
在学习过指针数组之后,我们可以用指针数组来模拟一个二维数组。具体原理如下所示。
有代码如下:
结果为:
拓展内容:
字符函数
strcpy
函数
-
作用:将源字符串(包括终止的空字符
\0
)复制到目标字符数组中。 -
示例:
-
char dest[20];
strcpy(dest, "Hello"); // dest 变为 "Hello\0" -
注意事项:
-
目标数组必须有足够的空间,否则会导致缓冲区溢出(安全隐患)。
-
更安全的替代函数是
strncpy
(可指定最大复制长度)。
-
strcat
函数
-
作用:将源字符串追加到目标字符串的末尾(覆盖目标字符串的终止符
\0
,并在新字符串末尾添加\0
)。 -
示例:
char dest[20] = "Hello";
strcat(dest, " World"); // dest 变为 "Hello World\0"
-
注意事项:
-
目标数组必须足够大,且已有内容加上新内容后不溢出。
-
更安全的替代函数是
strncat
(可指定最大追加长度)。
-
总结
-
strcpy
:复制字符串。 -
strcat
:连接字符串。
strncpy
函数
-
作用:将源字符串的前
n
个字符复制到目标字符数组中。如果源字符串长度不足n
,剩余部分用空字符(\0
)填充。
3. strstr
函数
4. strcmp
函数
总结
函数 | 作用 | 关键点 |
---|---|---|
strncpy | 安全复制字符串(限长度) | 需注意是否补 \0 ,防溢出 |
strdup | 动态复制字符串 | 需手动 free ,兼容性依赖环境 |
strstr | 查找子串位置 | 区分大小写,返回指针或 NULL |
strcmp | 按字典序比较字符串 | 返回值为0、正、负,区分大小写 |
掌握这些函数可高效处理字符串操作,但需注意内存管理和边界条件。
如何比较字符串之间的大小
-
语法:
char *strncpy(char *dest, const char *src, size_t n);
-
示例:
char dest[10]; strncpy(dest, "Hello World", sizeof(dest)); // 仅复制前9字符 + 末尾自动补'\0' // dest 内容为 "Hello Wo\0"
-
注意事项:
-
如果
src
长度超过n
,目标字符串不会自动添加终止符\0
,需手动处理。 -
常用于避免缓冲区溢出(比
strcpy
更安全)。
-
-
2.
strdup
函数 -
作用:动态分配内存并复制字符串,返回指向新字符串的指针。需手动释放内存(
free
)。 -
语法:
char *strdup(const char *s);
-
示例:
char *copy = strdup("Hello"); printf("%s\n", copy); // 输出 "Hello" free(copy); // 必须释放内存!
-
注意事项:
-
非C标准库函数,属于POSIX标准(需包含
<string.h>
或<stdlib.h>
)。 -
如果内存分配失败,返回
NULL
,需检查返回值。
-
-
作用:在字符串
haystack
中查找子串needle
的首次出现位置,返回指向该位置的指针,未找到则返回NULL
。 -
语法:
char *strstr(const char *haystack, const char *needle);
-
示例:
char *str = "Hello World"; char *found = strstr(str, "World"); // found 指向 "World" 的起始位置 if (found != NULL) {printf("Found at position: %ld\n", found - str); // 输出位置差 }
-
注意事项:
-
区分大小写(如需要不区分大小写,可用
strcasestr
,但非标准函数)。
-
-
作用:按字典顺序比较两个字符串,返回值为:
-
0:两字符串相等。
-
<0:
str1
小于str2
(按ASCII值逐字符比较)。 -
>0:
str1
大于str2
。
-
-
语法:
int strcmp(const char *str1, const char *str2);
-
示例:
if (strcmp("apple", "apple") == 0) {printf("Equal\n"); // 输出 } if (strcmp("apple", "banana") < 0) {printf("apple < banana\n"); // 输出('a' ASCII值 < 'b') }
-
注意事项:
-
比较时直到遇到
\0
或首个不同字符为止。 -
安全版本
strncmp
可指定比较的最大长度。
-
在 C 语言中,字符串本质上是字符数组(以空字符 \0`` 结尾),因此**不能直接使用
== 运算符比较字符串是否相等**(
==` 比较的是内存地址,而非内容)。以下是正确比较字符串的方法:
方法 1:使用 strcmp
函数
strcmp
是标准库函数(需包含 <string.h>
),按字典序逐字符比较两个字符串,返回值为:
0
:字符串内容完全相等。
< 0
:第一个字符串小于第二个字符串。
示例代码:
-
> 0
:第一个字符串大于第二个字符串。#include <stdio.h> -
#include <string.h>
int main() {
char str1[] = "Hello";
char str2[] = "Hello";
char str3[] = "World";if (strcmp(str1, str2) == 0) {
printf("str1 和 str2 相等\n"); // 输出此句
}if (strcmp(str1, str3) != 0) {
printf("str1 和 str3 不相等\n"); // 输出此句
}return 0;
}
注意事项:
-
比较时严格区分大小写(如
"hello"
和"HELLO"
会被认为不相等)。 -
若需要不区分大小写,可使用
strcasecmp
(Linux/UNIX)或_stricmp
(Windows),但非标准函数。
方法 2:使用 strncmp
函数
strncmp
可指定比较的字符数,适用于:
-
只比较字符串的前
n
个字符。 -
防止未终止的字符串导致内存越界。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "HelloWorld";
char str2[] = "HelloCat";
// 只比较前5个字符
if (strncmp(str1, str2, 5) == 0) {
printf("前5个字符相等\n"); // 输出此句
}
return 0;
}
方法 3:手动逐字符比较
如果不使用库函数,可以手动遍历字符数组逐一比较:
示例代码:
#include <stdio.h>
int isEqual(const char *s1, const char *s2) {
while (*s1 != '\0' && *s2 != '\0') {
if (*s1 != *s2) {
return 0; // 发现不同字符,返回不相等
}
s1++;
s2++;
}
// 检查是否同时到达末尾
return (*s1 == '\0' && *s2 == '\0');
}
int main() {
char str1[] = "Hello";
char str2[] = "Hello";
char str3[] = "Hell";
if (isEqual(str1, str2)) {
printf("str1 和 str2 相等\n"); // 输出此句
}
if (!isEqual(str1, str3)) {
printf("str1 和 str3 不相等\n"); // 输出此句
}
return 0;
}
关键总结
方法 | 适用场景 | 注意事项 |
---|---|---|
strcmp | 快速比较完整字符串 | 区分大小写,依赖 \0 终止符 |
strncmp | 比较前 n 个字符或防止越界 | 需明确指定长度 |
手动逐字符比较 | 理解底层原理或特殊需求(如自定义规则) | 需自行处理边界条件(如 \0 和非等长情况) |
常见错误
char *s1 = "abc";
char *s2 = "abc";
if (s1 == s2) { // 错误!比较的是指针地址,而非内容
printf("相等\n");
}
始终使用 strcmp
或 strncmp
比较字符串内容!
字符指针变量
下图所示的是一个字符数组,数组是可以修改的。
但是当你这么写的时候,
和数组的区别是二图的写法下字符串不可以修改。
且二图中的代码并不是把字符串赋值给了p,而是把字符串首字符的地址赋值给了p。
但是实际写的时候二图的写法不足够严谨,建议在char前面加上const修饰(二者含义没有变化,但是加上了更方便理解)
结果为
为什么会这样呢?
因为当我们创建str1和str2两个数组的时候,会分别的想内存申请两份空间(地址不同不能比),初始化为数组的时候会开辟出不同的字符串;而str3和str4创建的是同一个常量字符串,且常量字符串无法被修改(C\C++会把常量字符串储存到单独的内存区域内),当几个指针指向同一个内容的时候,实际上它们会指向同一块内存。因此只要内容一样,只存储一份就够了,大家共同使用就够了(节省内存)。
数组指针变量
数组指针变量是什么?
存放数组的指针,指向的是数组。
int arr[10]={0}的地址是什么呢?答案是&arr。
根据前面学到的知识,我们可知&数组名取出的就是数组的地址
&arr与p的类型完全一样
数组指针类型解析:
二维数组传参的本质
二维数组传参的时候形参是可以写成数组的。
二维数组实际上是一维数组的数组。而且二维数组的数组如果表示数组首元素的地址,那么它就是第一行的地址。
第一行的地址是一维数组的地址。
数组形式写法:
指针形式写法:
总结:二维数组传参时,形参可以写成数组也可以写成指针。
函数指针变量
函数指针变量的创建:
函数指针变量就是存储函数的地址,指向函数。
函数名和&函数名都是函数的地址。数组名是首元素的地址,&数组名是数组的地址。
函数指针变量的创建:
上图所示的指针变量后的 int 后面也可以加上形参名字(但是没有实际意义)。
类比上图还有一个:
把指针变量的名字去掉。剩下的就是它的类型。
函数指针变量的调用
通过函数指针变量来调用函数:
结果为:
以下写法与上文等效:
有两段有意思的代码
以下两段代码均来自于书籍:《C语言陷阱与缺陷》
代码1:
类比以上还有一个
代码2:
代码解释:
1.上面的是一次函数声明
2.函数名叫:signal,有两个参数,第一个参数类型是int,第二个参数类型是函数指针void(*)(int)。
3.该函数指针所指向的函数参数为int,返回类型为void。
typedef关键字
typedef是将复杂的类型重新命名的。通常使复杂的类型简单化。
如何定义两个指针呢?
函数指针的重新命名。
但是切记不要想当然的这样写:
类比以下,数组类型指针也是如此:
函数指针数组
函数指针数组的用途:
做一个计算器程序实现加减乘除:(这里仅仅是为了演示,具体层序运行导致的数据丢失不精确等问题暂时不予以考虑,感兴趣者可自行修改)
转移表
初等算法:
定义函数
代码骨干:
这样一个初等的计算器代码就写好了。
但是我们显然发现一个问题:当函数越来越多的时候,switch语句中case写的就越来越多,代码也越来越臃肿。
因此可以用函数指针数组来改进(定义函数的内容与上一个算法相同)
效果与上图相同。
但是这种算法也有缺点:函数指针数组内的类型都被写死了,如果要改变很麻烦
回调函数
利用函数指针,我们可以实现回调函数的效果。
回调函数的概念
通过函数指针调用的函数。
如果你将一个函数的指针作为参数传递给另外一个函数,当这个函数被用来调用其所指向的函数时,被调用的函数就是回调函数。
回调函数不是由该函数是现房直接调用的,而是在特定的事件或者条件发生的时候由另一方调用的,用于对该事件或条件进行响应。
方案一是直接调用A函数,方案二是将A函数指针传递给B函数。
利用回调函数,针对上面计算器程序的算法一,我们可以做出如下的优化:
定义函数:
代码骨架:
回调函数的机制:
qsort的使用
qsort是一个库函数。是用来排序的。基于快速排序算法实现的库函数,可以用来排序任意类型的数据.
上图中我们所写的冒泡排序算法中,可以排整型浮点型数据,但是不能排结构体数据!即只能排列同一类型的数据。
(这里将冒泡排序放到这里再次复习一下)
qsort的语法结构:
当p1指向的元素大于p2指向的元素的时候返回一个大于0的数字。
当p1指向的元素等于p2指向的元素的时候返回一个0
当p1指向的元素小于p2指向的元素的时候返回一个小于0的数字。
两个字符串比较大小不是比较长度,而是比较对应位置上字符的大小。比如在字符串“abcdefg”和字符串"abcqb"比较大小的时候,因为q的码值大于d,因此即使前面的字符串更长,前面的字符串后续的 f 比 b 更大,仍然是后面的字符串大于前面的字符串。
qsort函数排序代码示例:
头文件包含:
main函数主体:
qsort排列整型数据
qsort排列结构体数据:
模拟qsort函数对冒泡排序进行改造,使之实现对所有数据类型的排列
关于原来的qsort函数语法结构的分析如下:
因此,我们仿照qsort函数可以将冒泡排序的类型定义为如下
在第四个参数中之所以设计为int,是因为两个函数在进行比较过后,只会返回大于0、小于0、等于0三项就足够了。e1 和 e2用void*是为了适应所有类型的数据而设计。加const修饰是因为仅仅只是为了比较大小而不会修改 e1 和 e2 的值,所以加上const来使得代码更加严谨。
test2函数调用了bobbleSort_qsort函数,bobbleSort_qsort函数又将cmp_int函数指针作为第四个参数,在第一趟的时候,arr数组中1和3进入cmp_int函数,返回值为-2,不满足if语句的判断条件,因此flag仍然等于1,不进行数据交换,跳出内层循环。
当数据为9和2的时候,满足内层循环if语句的判断条件,因此进入swap函数进行数据交换。
代码:
头文件:
后续刚需的函数:
模拟的冒泡排序函数:
实现函数:
结果:
结构体(本次没有放置打印函数)
主函数:
以上就是本次指针的内容了。
感谢看到这里的读者朋友,看在如此大的内容量上可否给一个赞呢?感激不尽
但是关于指针方面的内容并没有就此结束。后续我们还会写一些经典的指针例题哦,敬请期待。
相关文章:
万字长篇————C语言指针学习汇总
经过一段时间的学习,我们已经接触到了C语言的很多知识了。不过目前我们接下来我们要接触C语言中一个最大的“门槛”:指针。 什么是指针? 在介绍指针之前,我们首先要明白变量与地址之间的关系。 举一个生活中的案例:一…...
day29图像处理OpenCV
文章目录 一、图像预处理6 图像色彩空间转换6.3灰色/BGR/HSV相互转化 7 彩图转灰图方法7.1 最大值法7.2 平均值法7.3 加权均值法7.4 案例 8 图像二值化处理8.1 阈值法(typecv2.THRESH_BINARY)8.2 反阈值法(THRESH_BINARY_INV)8.3 截断阈值法(THRESH_TRUNC)8.4 低阈值零处理(THR…...
Spring Boot 项目三种打印日志的方法详解。Logger,log,logger 解读。
目录 一. 打印日志的常见三种方法? 1.1 手动创建 Logger 对象(基于SLF4J API) 1.2 使用 Lombok 插件的 Slf4j 注解 1.3 使用 Spring 的 Log 接口(使用频率较低) 二. 常见的 Logger,logger,…...
KrillinAI:视频跨语言传播的一站式AI解决方案
引言 在全球内容创作领域,跨语言传播一直是内容创作者面临的巨大挑战。传统的视频本地化流程繁琐,涉及多个环节和工具,不仅耗时耗力,还常常面临质量不稳定的问题。随着大语言模型(LLM)技术的迅猛发展,一款名为Krillin…...
PDF处理控件Aspose.PDF指南:使用 C# 从 PDF 文档中删除页面
需要从 PDF 文档中删除特定页面?本快速指南将向您展示如何仅用几行代码删除不需要的页面。无论您是清理报告、跳过空白页,还是在共享前自定义文档,C# 都能让 PDF 操作变得简单高效。学习如何以编程方式从 PDF 文档中选择和删除特定页面&#…...
在 IntelliJ IDEA 中开发 Java Web 项目时,遇到包内明明存在某个类但类名仍然爆红(显示红色错误提示)
在 IntelliJ IDEA 中开发 Java Web 项目时,遇到包内明明存在某个类但类名仍然爆红(显示红色错误提示),而项目却能正常运行,重启 IDEA 后问题依旧,这通常是由以下原因及解决方法导致的: 1. 缓存…...
【4】k8s集群管理系列--harbor镜像仓库本地化搭建
一、harbor基本概念 Harbor是一个由VMware开源的企业级Docker镜像仓库解决方案,旨在解决企业在容器化应用部署中的痛点,提供镜像存储、管理、安全和分发的全生命周期管理。Harbor扩展了Docker Registry,增加了企业级功能,如…...
Active Directory域服务管理与高级应用技术白皮书
目录 一、Active Directory核心架构解析 1.1 AD域服务核心组件 1.2 域功能级别演进 1.3 AD LDS应用场景 二、企业级域环境部署最佳实践 2.1 域控制器部署规划 2.2 高可用架构设计 2.3 客户端入域优化 三、高级域管理技术 3.1 精细化权限管理 3.2 组策略深度配置 3.3…...
OCP中的OCS operator介绍及应用示例
一、OCS operator介绍 在 Red Hat OpenShift Container Platform(OCP4.8版之前,包含4.8) 中,OCS Operator(OpenShift Container Storage Operator) 是用于在 OpenShift 集群中部署、配置和管理 OpenShift …...
Linux-服务器添加审计日志功能
#查看audit软件是否在运行(状态为active而且为绿色表示已经在运行) systemctl start auditd #如果没有在运行的话,查看是否被系统禁用 (audit为0表示被禁用) cat /proc/cmdline | grep -w "audit=0" #修改/etc/default/grub里面audit=0 改为audit=1 #更新GRUB…...
ARM Cortex-M中断处理全解析
今天我们聊一聊ARM Cortex-M中断处理。在嵌入式系统中,中断是实现实时响应的核心机制。想象一下,如果没有中断: 按键按下时,系统可能忙于其他任务而错过响应通信数据到来时,可能因为没及时处理而丢失定时任务难以精确…...
douyin_search_tool | 用python开发的抖音关键词搜索采集软件
本软件工具仅限于学术交流使用,严格遵循相关法律法规,符合平台内容合法性,禁止用于任何商业用途! 抖音作为国内颇受欢迎的短视频社交平台,汇聚了大量用户群体和活跃用户。分析平台上的热门视频可用于市场调研和竞品分析…...
基于FreeBSD的Unix服务器网络配置
Unix系统版本 FreeBSD-10.1-i386 网络配置 1.配置网络ip及网关 #编辑配置文件 ee /etc/rc.conf #参照如下内容设置 ifconfig_em0”inet 192.168.1.189 netmask 255.255.255.0” defaultrouter”192.168.1.1” #回到命令模式 esc #保存 a a 2.配置dns #编辑配置文件 ee /etc/…...
Margin和Padding在WPF和CSS中的不同
CSS和WPF中 margin 与 padding 在方向上的规定基本一致,但在使用场景和一些细节上有所不同。 CSS - 方向规定: margin 和 padding 属性可以分别指定上、右、下、左四个方向的值。例如 margin:10px 20px 30px 40px; 表示上外边距为10px、右外边距为20…...
JVM 概述
JVM概述 JVM的全为 Java Virtual Machine,但是目前的 JVM 已经不再与任何语言进行深度耦合了,其本质就是运行在计算机上的程序,职责是运行处理 Java 字节码文件。 JVM 功能 解释和运行 JVM 会对字节码文件中的指令,实时的解释为…...
基于django云平台的求职智能分析系统(源码+lw+部署文档+讲解),源码可白嫖!
摘要 时代在飞速进步,每个行业都在努力发展现在先进技术,通过这些先进的技术来提高自己的水平和优势,招聘信息管理系统当然不能排除在外。求职智能分析系统是在实际应用和软件工程的开发原理之上,运用Python语言、爬虫技术以及Dj…...
在 Ubuntu 上通过 Docker 部署 Misskey 服务器
在这篇博客中,我们将探讨如何在 Ubuntu 上通过 Docker 部署 Misskey 服务器。Misskey 是一个开源的社交网络平台,支持丰富的社交功能,适合个人和小型社群使用。而 Docker 则是一个便捷的容器化平台,允许开发者轻松地打包、发布和运…...
Pytorch 第十五回:神经网络编码器——GAN生成对抗网络
Pytorch 第十五回:神经网络编码器——GAN生成对抗网络 本次开启深度学习第十五回,基于Pytorch的神经网络编码器。本回分享的是GAN生成对抗网络。在本回中,通过minist数据集来分享如何建立一个GAN生成对抗网络。接下来给大家分享具体思路。 本…...
gitlab如何查看分支的创建时间
在 GitLab 上查看分支创建时间,常规的界面不会直接显示,但可以通过以下几种方法查到准确时间: 方法一:通过 GitLab Web 界面查看首次提交时间(近似) 打开你的项目仓库。点击左侧的「Repository(…...
centos时间不正确解决
检查当前系统时间 date如果时间明显不正确,可以进一步检查硬件时钟(BIOS 时间): bash复制代码hwclock --show同步时间(推荐方式) 为了确保系统时间准确,建议使用 NTP(网络时间协议…...
ubuntu启动 Google Chrome 时默认使用中文界面,设置一个永久的启动方式
方法 :通过桌面快捷方式设置 编辑 Chrome 的桌面快捷方式: 找到您的 Google Chrome 快捷方式文件。如果是通过菜单启动,通常会在以下路径找到与 Chrome 相关的 .desktop 文件: sudo vim /usr/share/applications/google-chrome.d…...
opencv腐蚀的操作过程
在腐蚀操作的详细流程中,遍历图像的过程如下: 初始化: 设置一个起始位置(通常从图像的左上角开始)。 准备好结构元素(structuring element),它是一个小的矩阵,大小通常是…...
Docker--Docker镜像原理
docker 是操作系统层的虚拟化,所以 docker 镜像的本质是在模拟操作系统。 联合文件系统(UnionFS) 联合文件系统(UnionFS) 是Docker镜像实现分层存储的核心技术,它通过将多个只读层(Image Laye…...
HL7消息编辑器的使用手册
REDISANT 提供互联网与物联网开发测试套件 # 互联网与中间件: Redis AssistantZooKeeper AssistantKafka AssistantRocketMQ AssistantRabbitMQ AssistantPulsar AssistantHBase AssistantNoSql AssistantEtcd AssistantGarnet Assistant 工业与物联网࿱…...
技术与情感交织的一生 (六)
目录 食色性也 Z 姐 Pizza “修罗场” 之战 大二 下 EP 混乱 危机 撤退 离别 初创 重逢 食色性也 美食、美器、美女。追求美好的事物是人的天性。八部众里,天众界:因修行,有美食而无美女;阿修罗界:因产力…...
AI搜索引擎的局限性
# 揭秘AI搜索引擎的局限性与深度爬取技巧 > 摘要:本文深入分析了基于关键词的AI搜索引擎局限性,探讨了深网内容难以被发现的原因,并提供了一系列实用技巧来提高信息获取的全面性。无论是开发者、研究人员还是普通用户,了解这些…...
IPD项目管理的“黄金三角“在2025年是否需要重构?
——技术革命下的组织进化与实践创新 一、时空背景:IPD黄金三角的底层逻辑与时代挑战 IPD(集成产品开发)管理体系自1998年引入中国以来,其"黄金三角"——跨职能团队协作、结构化流程体系、决策评审机制——始终是企业…...
Jarpress 开源项目重构公告
项目背景 经过长达三个月的技术攻坚,我们正式宣布完成对九年历史开源项目的全面重构升级!原项目基于JFina框架开发,现采用SpringBootMyBatis技术栈重构,正式更名为Jarpress。 架构升级 采用最小组件依赖实现,减少系…...
Redshift 2025.4.1 版本更新:多平台兼容性与功能修复
2025 年 4 月 10 日,Redshift 发布 2025.4.1 版本(2025.04),聚焦宿主软件兼容性提升与核心功能修复,具体更新如下: 各平台适配与优化 Maya/3ds Max/Blender:新增对 Maya 2026、3ds Max 2026、…...
使用crxjs插件编写浏览器扩展插件遇到的问题 Waiting for the extension service worker...
目前最新的vitejs/plugin-vue和crxjs/vite-plugin不兼容,在crxjs官网有写 修改插件版本如下: "devDependencies": {"crxjs/vite-plugin": "^1.0.14","vitejs/plugin-vue": "^2.3.4","vite"…...
数据库学习通期末复习一
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习C语言的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
数据分析实战案例:使用 Pandas 和 Matplotlib 进行居民用水
原创 IT小本本 IT小本本 2025年04月15日 18:31 北京 本文将使用 Matplotlib 及 Seaborn 进行数据可视化。探索如何清理数据、计算月度用水量并生成有价值的统计图表,以便更好地理解居民的用水情况。 数据处理与清理 读取 Excel 文件 首先,我们使用 pan…...
生态环境影响评价全解析
生态环境影响评价的原则、方法、工作程序、指标选择、参数计算、模型模拟、报告编制 一 :生态环境影响评价的基本程序 生态环境影响评价的涵义、生态影响的类型;生态环境影响评价的原则、流程、等级确定及工作范围。 图1 空间尺度上长江对中华鲟的累积…...
【Netty篇】Netty的线程模型
目录 一、Netty 线程模型是啥?二、Netty 线程模型有啥作用?三、Netty 线程模型解决了什么问题?四、如何使用 Netty 线程模型?五、Netty 线程模型的优缺点?六、总结 🌟我的其他文章也讲解的比较有趣…...
PyTorch实现权重衰退:从零实现与简洁实现
一、权重衰退原理 权重衰退(L2正则化)通过向损失函数添加权重的L2范数惩罚项,防止模型过拟合。其损失函数形式为: 二、从零开始实现 1.1 导入库与数据生成 %matplotlib inline import torch from torch import nn from d2l imp…...
Webflux声明式http客户端:Spring6原生HttpExchange实现,彻底摒弃feign
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
python的strip()函数用法; 字符串切片操作
python的strip()函数用法 目录 python的strip()函数用法代码整体功能概述代码详细解释1. `answer["output_text"]`2. `.strip()`3. `final_answer = ...`字符串切片操作:answer[start_index + len("Helpful Answer:"):].strip()整体功能概述代码详细解释1…...
多模态大语言模型arxiv论文略读(二十一)
EgoPlan-Bench: Benchmarking Multimodal Large Language Models for Human-Level Planning ➡️ 论文标题:EgoPlan-Bench: Benchmarking Multimodal Large Language Models for Human-Level Planning ➡️ 论文作者:Yi Chen, Yuying Ge, Yixiao Ge, Mi…...
MCP学习资料
Anthropic 官方:https://modelcontextprotocol.io/introduction 中文站:https://mcpcn.com/docs/examples/...
《Training Language Models to Self-Correct via Reinforcement Learning》全文翻译
《Training Language Models to Self-Correct via Reinforcement Learning》 通过强化学习训练语言模型实现自我修正 Aviral Kumar ∗ , 1 { }^{\\*, 1} ∗,1, Vincent Zhuang ∗ , 1 { }^{\\*, 1} ∗,1, Rishabh Agarwal ∗ , 1 { }^{\\*}, 1 ∗,1, Yi Su ∗ , 1 { }^…...
Rust 之五 所有权、.. 和 _ 语法、引用和切片、Vec<T>、HashMap<K, V>
概述 Rust 的基本语法对于从事底层 C/C 开发的人来说多少有些难以理解,虽然官方有详细的文档来介绍,不过内容是相当的多,看起来也费劲。本文通过将每个知识点简化为 一个 DEMO 每种特性各用一句话描述的形式来简化学习过程,提高学…...
如何运行Vue 3 + Tauri + Rust 前端项目
Vue 3 Tauri Rust 前端项目运行需要安装以下工具和依赖: 1. 基本开发工具 Node.js (建议 LTS 版本) - 用于运行前端构建工具 包含 npm 或 yarn 包管理器下载地址 Rust 工具链 - Tauri 基于 Rust 构建 通过 rustup 安装安装命令: curl --proto https --tlsv1.2 -…...
Nature图形复现—Origin绘制顶刊水准的多组柱状图
多组柱状图(也称分组柱状图或簇状柱状图)是一种数据可视化图形,用于同时展示多个组别在不同子类别下的数据对比。其核心特点是通过并列的柱子将不同组别的数据排列在同一子类别下,便于直观比较。 本期教程以2022年发表于Nature的文…...
空格键会提交表单吗?HTML与JavaScript中的行为解析
在网页开发中,理解用户交互细节对于提供流畅的用户体验至关重要。一个常见的问题是:空格键是否会触发表单提交?本文将通过一个简单的示例解释这一行为,并探讨如何使用HTML和JavaScript来定制这种交互。 示例概览 考虑以下HTML代…...
详解@JsonFormat和@DateTimeFormat注解:处理日期格式化的利器
在Java开发中,尤其是Spring和Spring Boot项目中,日期时间类型(如Date、LocalDateTime等)的格式化问题经常困扰开发者。例如,前端传递的日期字符串如何转换为后端对象?后端返回的日期对象如何按指定格式序列化?这时候,@JsonFormat和@DateTimeFormat两个注解可以轻松解决…...
python-各种文件(txt,xls,csv,sql,二进制文件)读写操作、文件类型转换、数据分析代码讲解
1.文件txt读写标准用法 1.1写入文件 要读取文件,首先得使用 open() 函数打开文件。 file open(file_path, moder, encodingNone) file_path:文件的路径,可以是绝对路径或者相对路径。mode:文件打开模式,r 代表以…...
Python(16)Python文件操作终极指南:安全读写与高效处理实践
目录 背景介绍一、文件操作基础架构1. 文件打开模式详解 二、文件读取全攻略1. 基础读取方法2. 大文件处理方案3. 定位与截断 三、文件写入进阶技巧1. 基础写入操作2. 缓冲控制与实时写入 四、with上下文原理剖析1. 上下文管理协议2. 多文件同时操作 五、综合实战案例1. 加密文…...
Maven相关名词及相关配置
1、相关名词 1 Project: 任何你想build的事物,maven都可以认为他们是工程,这些工程被定义为工程对象模型(POM:Project Object Model)一个工程可以依赖其他的工程,一个工程也可以有多个子工程构成。 2 POM: 就是xml文件…...
【自动化测试】如何获取cookie,跳过登录的简单操作
前言 🌟🌟本期讲解关于自动化测试函数相关知识介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话…...
登录校验:保障系统安全访问的关键技术解析
摘要:本文围绕Tlias智能学习辅助系统的登录校验功能展开,深入剖析了实现登录校验的必要性,介绍了会话技术和统一拦截技术等关键实现思路,并对Cookie、Session和令牌技术三种会话跟踪方案进行了详细对比,旨在为系统的安…...