c语言的罗盘——指针!深入理解c语言指针及其应用(代码片段)

未见花闻 未见花闻     2022-12-21     291

关键词:

⭐️前面的话⭐️

在前面C语言的指南针——指针!指针与结构体的介绍C语言处理批量数据的好伙伴!数组!C语言数组的介绍与应用两篇文章中已经简要介绍了数组指针,字符串库函数和指针等内容,在这篇文章我们将继续深入了解有关字符串库函数和指针的探索。

📒博客主页:https://blog.csdn.net/m0_59139260?spm=1011.2124.3001.5343
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
✉️坚持和努力一定能换来诗与远方!
🙏作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
博主的码云gitee,平常博主写的程序代码都在里面。



1.字符指针与字符串

字符指针即指向字符类型的指针。

char* pch = NULLchar ch = 's';
pch = &ch;//字符指针

1.1字符指针与字符串的关系

在C语言中,并没有字符串这种数据类型,但是在编程中又常常遇到字符串,那在C语言中然后去表达字符串呢?
有两种方式,一是使用字符数组表示字符串,本质上就是定义一个char类型的数组变量去储存字符串,使用字符数组表示字符串得到的是一个字符串变量,它是能够被修改的;二是使用字符指针的方式去表达字符串,本质上就是使用一个char指针指向字符串的第一个字符,因为在输出字符串的时候计算机是以\\0为标志来打印字符串的。但是要注意的是,它和使用字符数组不同,字符数组定义的是一个字符串变量,而字符指针定义的是一个字符串常量,是不能进行修改的。

#include <stdio.h>

int main()

	char str1[40] = "Come on, China Olympic Games!";//使用字符数组定义字符串变量

	char* str2 = "Come on, China Olympic Games!";//使用字符指针定义字符串常量

	printf("str1:%s\\nstr2:%s\\n", str1, str2);
	return 0;

💡运行结果:

str1:Come on, China Olympic Games!
str2:Come on, China Olympic Games!

D:\\gtee\\C-learning-code-and-project\\test_813\\Debug\\test_813.exe (进程 6960)已退出,代码为 0。
按任意键关闭此窗口. . .

如果不小心对字符串常量修改,会引发读写访问冲突的异常,对于VS这个异常只有在调试的时候才能看得见而在编译的时候是不会报警告或者报错的,万一是在一个很大的工程中出现了这种问题,是很难察觉的,所以一般定义一个不能被修改的变量或常量的时候,一般在定义的数据类型前面加上一个const,这样如果不小心修改了这个const修饰的变量或常量,编译器在编译的时候就会报错,这样就能更容易地发现读写冲突异常的情况。

💡面试题:

#include <stdio.h>
int main()

    char str1[] = "hello str.";
    char str2[] = "hello str.";
    char* str3 = "hello str.";
    char* str4 = "hello str.";
    if (str1 == str2)
        printf("str1 and str2 are same\\n");
    else
        printf("str1 and str2 are not same\\n");

    if (str3 == str4)
        printf("str3 and str4 are same\\n");
    else
        printf("str3 and str4 are not same\\n");

    return 0;

使用字符数组表达的是字符串变量,本质就是数组,每个数组是独立储存的,所以就算两个数组储存相同的内容,但是两者地址一定是不相同的。
而使用字符指针表达的是字符串常量,所以内容相同,字符指针指向的就是同一个地址。
知道了这个区别,那这道面试题也能迎刃而解了。
💡运行结果:

str1 and str2 are not same
str3 and str4 are same

D:\\gtee\\C-learning-code-and-project\\test_813\\Debug\\test_813.exe (进程 16488)已退出,代码为 0。
按任意键关闭此窗口. . .

1.2常用的几个字符串库函数

在前面介绍字符数组的时候简要提到了这几个与字符串相关的库函数,现在我将详细介绍这些常用的库函数。

1.2.1输出输入字符串的函数

puts(字符数组);//输出
int puts(const char* string);
gets(字符数组);//输入
char* gets(char* buffer);


🔎puts:函数所在库为stdio.h;函数参数为const修饰的char*类型(使用const修饰是为了防止字符串被修改);函数返回值为int,如果函数执行失败则会返回EOF

🔎gets:函数所在库为stdio.h;函数参数为char*类型(从缓冲区读取一行字符串,buffer是缓冲区的意思);函数返回值为char*(字符串的第一个元素地址),函数不能传入空指针,如果函数执行失败会返回EOF

用puts和gets函数只能输出或输入一个字符串。

puts作用:将一个字符串(以′\\0′结束的字符序列)输出到终端。
用puts函数输出的字符串中可以包含转义字符。
在用puts输出时将字符串结束标志′\\0′转换成′\\n′,即输出完字符串后换行。
gets作用:从终端输入一个字符串到字符数组,并且得到一个函数值。该函数值是字符数组的起始地址。

1.2.2字符串连接函数

strcat(字符串1, 字符串2);
char *strcat( char *strDestination, const char *strSource );


🔎strcat:函数所在库为string.h;函数参数有两个,第一个为char*类型(目的字符串地址)第二个参数为const char*类型(被连接字符串的地址);函数返回值为char*(返回strDestination的元素地址)。
strcat函数将strSource追加到strDestination后面。

strcat作用:把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值——字符数组1的地址。
字符数组1必须足够大,以便容纳连接后的新字符串。
连接前两个字符串的后面都有′\\0′,连接时将字符串1后面的′\\0′取消,只在新串最后保留′\\0′。

1.2.3字符串复制函数

strcpy(字符串1, 字符串2);
char *strcpy( char *strDestination, const char *strSource );

🔎strcpy:函数所在库为string.h;函数参数有两个,第一个为char*类型(目的字符串地址)第二个参数为const char*类型(被复制字符串的地址);函数返回值为char*(返回strDestination的元素地址)。
strcpy函数将strSource复制给strDestination

strcpy作用:将字符串2复制到字符数组1中去。
字符数组1必须定义得足够大,以便容纳被复制的字符串2。字符数组1的长度不应小于字符串2的长度。
“字符数组1”必须写成数组名形式,“字符串2”可以是字符数组名,也可以是一个字符串常量。
若在复制前未对字符数组1初始化或赋值,则其各字节中的内容无法预知,复制时将字符串2和其后的′\\0′一起复制到字符数组1中,取代字符数组1中前面的字符,未被取代的字符保持原有内容。
不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组。字符数组名是一个地址常量,它不能改变值,正如数值型数组名不能被赋值一样。
可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去。
将str2中最前面2个字符复制到str1中,取代str1中原有的最前面2个字符。但复制的字符个数n不应多于str1中原有的字符(不包括′\\0′)。

1.2.4字符串比较函数

strcmp(字符串1, 字符串2);
int strcmp( const char *string1, const char *string2 );


🔎strcmp:函数所在库为string.h;函数参数有两个,第一个为constchar*类型(第一个字符串地址)第二个参数为const char*类型(第二个字符串的地址);函数返回值为int(相同则返回0,字符串1>字符串2则返回一个正整数,字符串1<字符串2则返回一个负整数)。

💡对两个字符串比较不能直接用str1>str2进行比较,因为str1和str2代表地址而不代表数组中全部元素,而只能用 (strcmp(str1,str2)>0)实现,系统分别找到两个字符数组的第一个元素,然后顺序比较数组中各个元素的值。

strcmp作用:比较字符串1和字符串2。
字符串比较的规则是: 将两个字符串自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同的字符或遇到′\\0′为止。
(1) 如全部字符相同,则认为两个字符串相等;
(2) 若出现不相同的字符,则以第1对不相同的字符的比较结果为准。
比较的结果由函数值带回。
(1) 如果字符串1与字符串2相同,则函数值为0。
(2) 如果字符串1>字符串2,则函数值为一个正整数。
(3) 如果字符串1<字符串2,则函数值为一个负整数。

1.2.5测字符串长度的函数

strlen(字符串);
size_t strlen( const char *string );//size_t本质上是unsigned int 类型


🔎strlen:函数所在库为string.h;函数参数为const char*类型(需要被计算字符串长度的字符串地址);函数返回值为unsigned int(size_t)(返回传入字符串长度大小)。

strlen作用:测试字符串长度的函数。函数的值为字符串中的实际长度(不包括′\\0′在内)。

1.2.6转换为大小写的函数

strlwr(字符串);//大写字母转小写字母
char *_strlwr( char *string );
strupr(字符串)//小写字母转大写字母


🔎strlwr:函数所在库为string.h;函数参数为char*类型(需要被转换成小写的字符串地址);函数返回值为char*(返回传入字符串地址)。

🔎strupr:函数所在库为string.h;函数参数为char*类型(需要被转换成大写的字符串地址);函数返回值为char*(返回传入字符串地址)。

作用:
srtlwr将字符串中大写字母换成小写字母。
strupr将字符串中小写字母换成大写字母。

2.指针数组与数组指针

2.1指针数组

指针数组就是数组元素类型为指针的数组。

int* arr1[10]; //整形指针的数组
char* arr2[40]; //一级字符指针的数组
char** arr3[50];//二级字符指针的数组

2.2数组指针

数组指针就是指向一个数组的指针。要弄清楚数组指针是什么,首先必须知道数组指针是指针还是数组。我们从概念名字上进行分析,就能知道它是指针而不是数组。
我们以类比的方式来搞清楚数组指针究竟是什么?
我们已经学习过整型指针int*,浮点数指针float* double*,我们以类型名+变量名的方式定义一个相应类型的指针。

a = 8;
b = 2.2;
int* pa = &a;//整型指针
double* pb = &b;//双精度浮点型指针

同理数组指针也是如此,不过需要与指针数组区分开来

int* arr[10];//整型指针数组,是一个数组,存放了10个整型指针类型元素
int (*parr)[10];//这就是一个数组指针,是不是和指针数组有点像呢?
//因为[]优先级高于*,如果不加括号,变量名首先会与[]结合表示成一个数组
//如果(*变量名),那变量名首先会与*结合就表示一个指针

int (*parr)[10]表示一个指针,指向一个数据类型为int元素个数为10的一个数组,所以parr是一个数组指针变量。

2.3数组名与&数组名

数组名一般情况下指的是数组首元素地址,与数组名[0]等价。但是有二般情况,使用sizeof(数组名),这里的数组名指的是全数组整体;&arr并不是指数组名存放的地址的地址,而是指一个数组整体的首地址。数组名+1加的是该数组存放元素的数据类型的大小,如果为int类型的数组,那就是加4,而&arr+1加的是数组整体的大小,如果一个int类型的数组有10个元素,那就是加40。

3.数组传参与指针传参

3.1数组传参

3.1.1一维数组传参

对于test1 test2函数形参可以这么写呢?

#include <stdio.h>
int main()

	int arr1[10] =  1,2,3,4,5,6,7,8,9,0 ;
	int* arr2[10] =  0 ;
	int i = 0;
	for (i = 0; i < 10; i++)
	
		arr2[i] = &arr1[i];
	
	test1(arr1);
	test2(arr2);
	return 0;

//test1
 void test1(int arr[]);//方式1
 void test1(int arr[10]);//方式2
 void test1(int* arr);//方式3
//test2
void test2(int* arr[]);//方式1
void test2(int* arr[10]);//方式2
void test2(int** arr);//方式3

3.1.2二维数组传参

对于test函数形参可以这么写呢?

#include <stdio.h>
int main()

	int arr[4][3] =  0 ;
	test(arr);
	return 0;

void test(int arr[4][3]);//方式1
void test(int arr[][3]);//方式2
void test(int arr[][]);//不能省略列数,错误
//对于二维数组来说,每个元素指的是每一行,首元素指的是第一行数组,所以可以使用数组指针传参
void test(int (*arr)[3]);//方式3

3.2指针传参

3.2.1一级指针传参

一个函数test(int* p)能接受什么参数?

int arr[10] = 0;
test(arr);//1.数组名
int a = 10;
int* pa = &a;
test(pa);//或
test(&a);//2.一级指针

3.2.2二级指针传参

一个函数test(int** p)能接受什么参数?

int n = 10;
int* p = &n;
int** pp = &p;
int* arr[10] = 0;
test(arr);//1.整型指针数组
test(pp);
test(&p);//2.二级指针

4.函数指针与函数指针数组

4.1函数指针

如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段内存空间有一个起始地址,也称为函数的入口地址。每次调用函数时都从该地址入口开始执行此段函数代码。
函数名就是函数的指针,它代表函数的起始地址。
函数名与数组名有点相似,对函数名取地址得到的还是函数的地址。

#include <stdio.h>
void test()

	printf("hehe\\n");

int main()

	printf("%p\\n", test);
	printf("%p\\n", &test);
	return 0;

💡运行结果:

004713B1
004713B1

D:\\gtee\\C-learning-code-and-project\\test_813\\Debug\\test_813.exe (进程 20032)已退出,代码为 0。
按任意键关闭此窗口. . .

可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。例如:

int (*p)(int,int);

定义p是一个指向函数的指针变量,它可以指向函数类型为整型且有两个整型参数的函数。此时,指针变量p的类型用int (*)(int,int)表示。

⭐️函数指针定义通式:类型名 (*指针变量名)(函数参数表列)
⭐️对于函数指针你要注意以下几点:

🍋:定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。
🍋:如果要用指针调用函数,必须先使指针变量指向该函数。
🍋:在给函数指针变量赋值时,只须给出函数名而不必给出参数。
🍋:用函数指针变量调用函数时,只须将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。
🍋:对指向函数的指针变量不能进行算术运算,如p+n,p++,p–等运算是无意义的。
🍋:用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。
小栗子

#include <stdio.h>

int Add(int x, int y)

	return x + y;

int main()

	int a = 2;
	int b = 6;
	int (*p)(int, int) = Add;
	int c = Add(a, b);
	int d = (*p)(a, b);
	printf("%d\\n%d", c, d);
	return 0;

💡运行结果:

8
8
D:\\gtee\\C-learning-code-and-project\\test_813\\Debug\\test_813.exe (进程 21292)已退出,代码为 0。
按任意键关闭此窗口. . .

💡分析两个有趣的代码

#include <stdio.h>
int main()

	//代码1 
	(*(void (*)())0)();
	//代码2
	void (*signal(int, void(*)(int)))(int);
	return 0;

🔑代码1:
void (*)()是一个参数为空返回值为空的函数指针类型,(void (*)())0意思是将0强制转换成这种指针类型,(* (void (*)())0)()解引用被转换过的0执行在该地址的函数。
🔑代码2:
定义了一个名为signal的函数,其中参数为整型int和参数为int 返回值为空的函数指针void(*)(int),返回值为void (*)(int)类型的函数指针。
可以使用typedef将这个代码简化:

typedef void (*pf)(int)
void (*signal(int, void(*)(int)))(int);
//相当于
pf signal(int, pf);

4.2函数指针数组及其在计算器的应用

//函数指针
int (*func)(int, int);
//指针数组
int* arr[10];
//函数指针数组
int (*funcarr[10])(int, int);
//函数指针数组的指针
int (*(*pfuncarr)[10])(int, int);

函数指针数组用途:转移表

🌽栗子:整型加减乘除计算器

菜单及其加减乘除函数

#include <stdio.h>
void menu()

	printf("**********************************\\n");
	printf("**********************************\\n");
	printf("*******        1.add       *******\\n");
	printf("*******        2.sub       *******\\n");
	printf("*******        3.mul       *******\\n");
	printf("*******        4.div       *******\\n");
	printf("*******        0.exit      *******\\n");
	printf("**********************************\\n");
	printf("**********************************\\n");

	

int Add(int x, int y)//int (*)(int, int)

	return x + y;


int Sub(int x, int y)//int (*)(int, int)

	return x - y;


int Mul(int x, int y)//int (*)(int, int)

	return x * y;


int Div(int x, int y)//int (*)(int, int)

	return x / y;


void Calc(int(*pf)(int, int))

	int x = 查看详情  

c语言进阶笔记深入了解进阶指针(代码片段)

目录前言指针进阶字符指针指向常量字符串的指针指针数组指针数组打印数组内容数组指针对数组指针的理解&数组名和数组名数组指针的使用数组参数、指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指... 查看详情

c语言进阶笔记深入了解进阶指针(代码片段)

目录前言指针进阶字符指针指向常量字符串的指针指针数组指针数组打印数组内容数组指针对数组指针的理解&数组名和数组名数组指针的使用数组参数、指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指... 查看详情

c语言之深入解析如何理解指针和结构体指针指针函数函数指针(代码片段)

...是变量。既然指针是变量,那必然会有变量类型。在C语言中,所有的变量都有变量类型,整型、浮现型、字符型、指针类型、结构体、联合体、枚举等,这些都是变量类型。变量类型的出现是内存管理的必然结果... 查看详情

c真正理解二级指针

...二级指针及其使用  正文如下:    指针是C语言的灵魂,我想对于一级指针大家应该都很熟悉,也经常用到:比如说对于字符串的处理,函数参数的“值,结果传递”等,对于二级指针或者多级指针,我想理解起来... 查看详情

c语言指针基本概念及其指针变量的定义是啥

...)所指向的变量的数据类型。扩展资料:与其他高级编程语言相比,C语言可以更高效地对计算机硬件进行操作,而计算机硬件的操作指令,在很大程度上依赖于地址。指针提供了对地址操作的一种方法,因此,使用指针可使得C语... 查看详情

对c指针的理解(代码片段)

...觉写的特别好,摘录出来加深理解并作为分享。指针是C语言的一个核心特色,以一种统一的方式,对不同数据结构中的元素产生引用。下面重点介绍一些指针和它们映射到机器代码的关键原则。每个指针都对应一个类型。这个... 查看详情

06深入理解c指针之---指针类型和长度

  该系列文章源于《深入理解C指针》的阅读与理解,由于本人的见识和知识的欠缺可能有误,还望大家批评指教。  如果考虑到程序的可移植性和跨平台性时,指针长度就是一个问题,需要慎重处理。一般情况下,数据指... 查看详情

深入理解c语言的指针(代码片段)

一、指针的优先级括号()的优先级最高,其次是数组[],然后是剩余的*,最后是类型。指针p与优先级高的先结合,对于比较复杂的指针,结合后就视为一个整体temp,然后再与剩下的结合进行分析,慢慢... 查看详情

深入理解c语言的指针(代码片段)

一、指针的优先级括号()的优先级最高,其次是数组[],然后是剩余的*,最后是类型。指针p与优先级高的先结合,对于比较复杂的指针,结合后就视为一个整体temp,然后再与剩下的结合进行分析,慢慢... 查看详情

深入理解c语言的指针(代码片段)

一、指针的优先级括号()的优先级最高,其次是数组[],然后是剩余的*,最后是类型。指针p与优先级高的先结合,对于比较复杂的指针,结合后就视为一个整体temp,然后再与剩下的结合进行分析,慢慢... 查看详情

c语言中的定位,谈谈对指针的基本理解(代码片段)

在C语言中和地址相遇-指针1、指针是什么?3、指针和指针类型3、野指针4、如何规避野指针5、指针运算6、指针和数组7、二级指针8、指针数组1、指针是什么?在计算机科学中,指针(Pointer)是编程语言中的... 查看详情

c语言中的定位,谈谈对指针的基本理解(代码片段)

在C语言中和地址相遇-指针1、指针是什么?3、指针和指针类型3、野指针4、如何规避野指针5、指针运算6、指针和数组7、二级指针8、指针数组1、指针是什么?在计算机科学中,指针(Pointer)是编程语言中的... 查看详情

c语言关于指针函数与函数指针个人理解

1,函数指针  顾名思义,即指向函数的指针,功能与其他指针相同,该指针变量保存的是所指向函数的地址。假如是void类型函数指针定义方式可以是void(*f)(参数列表);亦可以先用typedef void(*F)(参数列表),Ff.但要注意函数指... 查看详情

c语言指针容易混淆的一些应用(代码片段)

数组名称等价于指针intiArry[]=1,2,3,4,5;int*p;p=iArry;//数组名iArry等价于一个指向该数组的指针//方括号[]数组下标运算符号相当于取址//怎么理解呢,iArry[1]等价于&iArry+1或*p+1;//数组名iArry是指向该数组第一个元素的... 查看详情

c语言进阶笔记深入了解进阶指针(代码片段)

目录前言指针进阶字符指针指向常量字符串的指针指针数组指针数组打印数组内容数组指针对数组指针的理解&数组名和数组名数组指针的使用数组参数、指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指... 查看详情

c语言指针基本概念及其指针变量的定义是啥

语言中,指针是一种类型,被称为“指针类型”。指针类型描述的是一个地址,这个地址指向内存中另外一个对象的位置。简单地说,指针表示的是它所指向对象的地址。1、比较point,*point,&point三者的区别对于int*point;point:是... 查看详情

彻底搞定c语言指针,初学者必备

1.语言中变量的实质要理解C指针,我认为一定要理解C中“变量”的存储实质,所以我就从“变量”这个东西开始讲起吧!先来理解理解内存空间吧!请看下图:内存地址→ 6     7  8     9  10 ... 查看详情