第9-10:C语言指针字符串结构体

发布时间:2026/11/5 8:02:05
第9-10:C语言指针字符串结构体


在指针中,我学习了利用指针操作函数,查询、存储数据,返回多个值,将函数结果与计算分开,和指针高级,指针高级其中有指针运算,运算有无意义,野指针和悬空指针,void类型指针,数组指针,利用索引遍历数组得二种方法,利用指针遍历数组二种方法,函数指针。

在字符串中,我学习了字符串定义格式,输入字符串遍历,字符串数组,字符串常见函数、练习用户登陆和统计次数。

在结构体中,我学习了结构体定义格式,结构体数组,起别名,结构体作为函数参数传递,结构体嵌套

指针

是什么:内存地址
指针本质是"地址",它的核心作用是定位内存:
作用:
1. 唯一标识一块内存——通过地址能精确找到某个变量/数据
2. 作为参数传递——把地址传给函数,让函数能操作原数据
3. 作为返回值,返回多个值——函数返回地址,让调用者访问某块内存
4. 构成数据结构——链表节点靠"下一个节点的地址"串联

利用指针同时返回多个值//定义一个函数,求数组中的最大值和最小值,并进行返回 //1.定义数组 { int arr[] = { 12,17,23,549,7,62,4 }; int len = sizeof(arr) / sizeof(int); //2.定义变量最大值和最小值 int max = arr[0]; int min = arr[0]; //3.调用函数 getmaxandmin(arr, len, &max, &min); printf("数组最大值为;%d\n", max); printf("数组最小值为;%d\n", min); } void getmaxandmin(int arr[], int len,int* max,int* min) { //求数组的最大值 * max = arr[0]; for (int i = 1;i < len;i++) { if (arr[i] >* max) { *max = arr[i]; } } //求数组的最小值 * min= arr[0]; for (int i = 1;i < len;i++) { if (arr[i] <*min) { *min = arr[i]; } } } */

指针变量

是什么:专门用来保存地址的变量
作用:
1.间接访问-解引用
2.函数传地址调用
3.动态内存管理
4.高效管理数组/字符串
5.构建复杂数据结构
6.实现多态/回调(函数指针)

利用指针存储数据,修改数据//1.定义一个变量 int a = 10; //2.定义一个指针去指向变量a int* p = &a; //3.利用指针去获取变量a中的值 printf("%d\n", *p); //获取a的数据 printf("%d\n", p); //获取a的内存地址 //4.利用指针去存储变量/修改变量 *p = 200; //5.输出打印 printf("%d\n", *p); //获取修改后a的数据 printf("%d\n", a); //修改后a的值

指针高级

指针运算:

步长:指针移动一次的字节个数,步长的大小与数据类型有关
int -4 char-1 short-2 long-4 longlong-8

int a = 10; int* p = &a; //作用;用指针p记录变量a的地址 printf("%p\n", p); //输出是内存地址 printf("%p\n", p+1); printf("%p\n", p-1);
运算的有意义/无意义操作

有意义操作:
1.指针跟整数进行加、减操作(每次移动n个步长)
2.指针跟指针减操作(可知二指针间隔步长)

int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; //获取0索引内存地址 int* p1 = &arr[0]; //通过指针p获取数据 printf("%d\n", *p1); printf("%d\n", *(p1+1)); //获取5索引内存地址 int* p2 = &arr[5]; //p2-p1间隔多少步长 printf("%d\n", p2 - p1); // 五个步长,20个字节

无意义操作:
1.指针跟整数进行乘、除操作(这是错误的,因为此时指针指向不明)
2.指针跟指针进行加、减、乘、除操作

指向不明的指针:

野指针:指针指当空间未分配
悬空指针:指针指向空间已经分配,但是被释放了

void类型指针:

格式:void*p,这个指针不表示任何类型,无步长概念
它只可以记录地址值,可接受任何地址。
不可以获得变量中的数据,也不可以进行计算
不同类型之间的指尖不可以进行互相赋值,
但void类型可以,void类型可接受任意类型指针记录的内存地址

注意事项在 C 语言中,void*(无类型指针)是可以自动转换成任何其他类型的指针的(隐式转换) 而C++ 是一种非常严格的强类型语言。为了防止犯错,它彻底取消了 void* 到其他具体类型指针的隐式转换 编译器行为:一旦 VS 看到 .cpp,它就会调用 C++ 编译器来编译这段代码,它也会拿 C++ 的严格标准来审查你 所以.cpp无法自动转换任何其他类型的指针。如果改成.c,再次编译,VS 就会调用 C 编译器,就可实现自动转换void类型互换数据//1.定义二个变量 int a= 10; short b = 20; //2.定义二个指针 int* p1 = &a; short* p2 = &b; //利用void类型直接互换 void* p3 = p1; void* p4 = p2;
二级指针:

格式:数据类型**指针名
其中指针类型跟指向空间数据类型保持一致
作用:
可以操作一级指针记录的地址
(可以获取一级指针的地址,也可以获取一级指针的数据,也可以修改一级指针的内存地址)

//定义变量 int a = 10; int b = 20; //定义一级指针 int* p = &a; //定义二级指针指向一级指针p int** q = &p; //作用1.利用二级指针修改改一级指针里记录的内存地址 *q = &b; //作用2.利用二级指针获取变量中记录的值 printf("%p\n", &a); printf("%p\n", &b); printf("%p\n", p); printf("%p\n", *q); //解引1;获取一级指针记录的内存地址 printf("%d\n", **q); //解引2;获取到变量中的数据 打印结果

数组指针:

是什么:指向数组的指针
作用;
方便操作数组中的各种数据
细节;arr直接参与计算的时候,会自动退化为第一个元素的指针
特殊情况:
1.sizeof参与计算时,不会退化,还是整体(eg:算数组长度)
2.&arr获取地址时,不会退化

int arr[] = { 1,2,3,4,5,6,7,8,9, }; int len = sizeof(arr) / sizeof(int); //实际上获取的是数组的首地址,然后再把首地址赋值给*p int *p=arr int* p = &arr[0];细节//sizeof 运算的时候,不会退化,arr还是整体 printf("%zu\n", sizeof(arr)); //36个字节 //1.首地址对比 printf("%p\n", arr); //一般用该种更多 printf("%p\n", &arr); //2.步长对比 printf("%p\n", arr+1); //4 printf("%p\n", &arr+1); //36
二维数组:

是什么:把多个小地址放到一个大数组中
定义格式:

int arr[m][n] =m是二维数组的长度,n是一维数组的长度{成员1; 成员2; ...... };

利用索引遍历二维数组:

第一种方法,局限于二维数组中每一个一维数组长度相同//1.定义二维数组 int arr[3][5] = { { 1,2,3,4,5 }, { 2,3,4,5,6 }, { 3,4,5,6,7 } }; for (int i = 0;i < 3;i++) //i;依次表示二维数组中的索引 { //内循环;遍历每一个一维数组 for (int j = 0;j < 5;j++) //j;依次表示一维数组中的索引 { printf("%d", arr[i][j]); } printf("\n"); //当内循环结束,表示一维数组遍历完毕 }
第二种方法:允许每一个一维数组长度不一样核心思路:事先先把所有一维数组定义完毕,再放入二维数组当中 //1.定义三个一维数组 int arr1[3] = { 1,2,3 }; int arr2[4] = { 1,2,3,4 }; int arr3[5] = { 1,2,3,4,5 }; //预先计算每一个一维数组的长度 int len1 = sizeof(arr1) / sizeof(int); int len2 = sizeof(arr2) / sizeof(int); int len3 = sizeof(arr3) / sizeof(int); //再定义一个数组表示每一个一维数组的长度 int len[3] = { len1,len2,len3 }; //2.把三个一维数组放到二维数组中 //数组的数据类型与内部存储的元素类型保持一致 int* arr[3] = { arr1,arr2,arr3 }; 指针数组 //此时二维数组存储的是一维数组的int类型的内存地址, arr数组元素类型是指向整数的指针,也就是int* for (int i = 0;i < 3;i++) { for (int j = 0;j < len[i];j++) //len[i]:依次表示三个一维数组的长度,该处可体现长度不一样,该如何表示 { printf("%d", arr[i][j]); } printf("\n"); }

利用指针遍历二维数组:

第一种方法:利用二维数组+指针遍历int arr[3][5] = { { 1,2,3,4,5 }, { 2,3,4,5,6 }, { 3,4,5,6,7 } };//获取二维数组的指针 int(* p)[5] = arr; 最关键的一步数组----指针//数组指针类型是与数组内部存储元素的数据类型保持一致 //二维数组里存的是一维数组,且一维数组的数据类型是int,又一维数组有5个int元素 for (int i = 0;i < 3;i++) { //内循环:遍历一维数组 for (int j = 0;j < 5;j++) { printf("%d", *(*p + j)); } printf("\n"); //移动二维数组的指针,继续遍历下一个一维数组 //(如果不移动指针,遍历的一直都是第一个一维数组) p++; }
第二种方法:指针数据+指针遍历//1.定义三个一维数组 int arr1[3] = { 1,2,3 }; int arr2[3] = { 2,3,4 }; int arr3[3] = { 3,4,5 }; //2,把这三个一维数组的内存地址,放到二维数组当中 int* arr[3] = { arr1,arr2,arr3 }; //此时这个二维数组是一个指针数组,三个元素都是指针int*//3.获取指针 int* * p = arr; 此时arr已经是指针了,所以这里是二级指针//与方法一唯一不同就是获取指针----int(*p)[5] = arr; //4.循环遍历数组 for (int i = 0;i < 3;i++) { for (int j = 0;j < 3;j++) { printf("%d", *(*p + j)); } //换行 printf("\n"); //移动指针 p++; }
函数指针:

定义格式:返回值类型*(指针名)(形参)=函数名 调用函数格式:1.调用无参函数:指针名(); 2.调用有参函数:数据类型 变量名=指针名(实参); 这里定义变量是用来接受返回值
作用:
利用函数指针,动态调用函数

//1.定义二个指针指向二个函数 void (*p1) ()= method1; int (*p2) (int, int) = method2; //2.利用函数指针调用函数 p1(); int num=p2(10,20); //定义一个临时变量接住它 printf("%d\n", num); } void method1() //无返回值 { printf("method1\n"); } int method2(int num1, int num2) //有返回值 { printf("method2\n"); return num1 + num2;

字符串

获取字符串二种格式:

1.字符数组+双引号

char str1[4] = "abc";

利用该方法可以修改字符串的内容


2.指针+双引号

char* str2 = "abcd";

利用该方法,会把底层的字符放在只读常量区
只读常量区特点:1.内容不可以修改
2.里面定义的字符串是可以复用的,
(eg:创建abc时,会检查只读常量区有无abc,如有,不会创建新的,而是进行复用 日无,则会创建新的

字符串数组
在这里我们需要注意,当我们遍历的是字符串数组时,利用%s,%s是专门用来接收地址的,当在打印printf时,printf拿到地址会自己去找需要的字符串,根本不需要解引用*,如加了*,是会报错的 普通整数 (int) %d 具体的数字值 需要 (*p) 单个字符 (char) %c 具体的字符值 可加可不加 (c 或 *p 都行) 整个字符串 (char*) %s 字符串的首地址(指针) 绝对不能加 (str)//定义一个二维数组用来存储多个学生的名字,并遍历// 第一种方式(二维数组)char strarr[5][100] = { "xuanqi", "xuanqii", "xuuanqi", "xuannqi", "xuanqiqi" }; //2.遍历二维数组 for (int i = 0;i < 5;i++) //i:表示二维数组中每一个索引 {char* str= strarr[i];//strarr[i]表示二维数组中每i行一维数组的首地址,利用char*str指针来接住这个地址printf("%s\n",str); //数组strarr是一块连续的500字节的内存,每次i++,在内存中的跨度是100个字节 }//第二种方式//把五个字符串的指针,放到一个数组中----(指针数组)char* strarr2[] = { "xuanqi", "xuanqii", "xuuanqi", "xuannqi", "xuanqiqi" }; for (int i = 0;i < 5;i++) //i:表示二维数组中每一个索引 {char* str= strarr2[i];//strarr2[i]表示指针数组中第i个元素,这个元素本身就是一个char*类型的指针printf("%s\n",str); //数组strarr2是一块连续的40字节的内存,存储5个指针,每次i++,在内存中的跨度是8个字节 }
字符串常见函数

(在使用以下函数时,应添加头文件string.h)
strlen-获取字符串长度
strcpy-复制字符串
strcat-拼接二个字符串
strcmp-比较二个字符串
strlwr-字符串小写
str字符串大写

char* str1 = "abc"; //底层会把字符数组放在只读常量区(1.只可读不可修改2.复用) char str2[100] = "abcd"; //可修改字符串内容 char str3[5] = { 'x' , 'a' ,'q','i','\0' };需要注意每个函数的使用格式和细节

结构体

结构体:

是什么:可理解为自定义的数据类型,由一批数据组合而成的结构性数据,里面的每一个数据都是结构体的成员。

书写位置:函数里面--局部,只可在本函数中使用---函数外面--全局,在所有函数中均可使用

格式:struct 结构体名字 { 成员1; 成员2; ..... }struct friend{ char name[100]; int age; char gender; double height; }; int main() { //使用结构体 //1.定义一个朋友类型的变量struct friendf1; //2.给朋友赋值strcpy(f1.name, "徐安琪1");//name是一个数组,数组不可以整体进行赋值,只能挨个元素赋值或者进行字符串函数复制 f1.age = 18; f1.gender = 'F'; //在这里定义gender,没有方括号,只是一个普通的字符变量,可以直接进行赋值 f1.height = 1.68; //3.输出打印 printf("朋友的名字叫做:%s\n",f1.name); printf("朋友的年龄为:%d\n",f1.age); printf("朋友的性别为:%c\n",f1.gender); printf("朋友的身高为:%.2f\n",f1.height);
结构体数组
struct student { char name[100]; int age; }; int main() { //1.定义三个学生,并直接进行赋值struct student stu1 = { "xuanqi",18 }; struct student stu2 = { "xuanqii",19 }; struct student stu3 = { "xuaanqi",20 }; //2.把三个学生放入到数组中struct student arr[3]= { stu1,stu2,stu3 }; //3.遍历数组 for (int i = 0;i < 3;i++) { struct student temp = arr[i]; printf("学生信息为:姓名为%s 年龄为%d\n",temp.name, temp.age); } }

在上述二个代码中体现了二种结构体给变量赋值

1.一个一个赋值:struct friend stu1; strcpy(f1.name, "徐安琪1"); stu1.age = 18; stu1.gender = 'F';

2.批量赋值:struct student stu1 = { "xuanqi",18 };

起别名
格式: typedef struct 结构体名字 { 成员1; 成员2; ... }别名;当起了别名,在主函数定义的时候可以省略 struct 结构体名字,直接使用别名即可
结构体最为函数参数传递

二种情况:1.传递结构体中的数据值

2.传递结构体的地址值

传递推荐用指针传递(传地址)---因为利用指针传递结构体地址,可以直接在函数内修改原结构体,且避免拷贝

typedef struct student { char name[100]; int age; }S; void method(S* p); //使用函数,且函数里面使用到结构体,所以申明必须写在结构体下面 int main() { //1.定义一个学生 S stu1; //2.给学生赋初始值 strcpy(stu1.name, "aaa"); stu1.age = 0; //3.输出打印 printf("学生的初始数据姓名为%s 年龄为%d\n", stu1.name, stu1.age); //4.调用函数 method(&stu1); //5.输出打印 printf("学生的修改后姓名为:%s 年龄为:%d\n", stu1.name, stu1.age); } void method(S*p)//S*p==S*stu1,二者都是指针变量,//无论是p还是stu1都是用来存内存地址的,所以哪种表达都可以,看自己那种可以理解 { printf("接收到main函数中学生的初始数据为:%s,%d\n",(*p).name,(*p).age); //为什么这里*p要小括号,因为.(成员访问运算符)优先级高于*(解引用运算符) //我们应先执行(*p),先把指针p指向的结构体变量stu1取出来, //再对这个取出来的结构体变量stu1中成员name进行访问 //修改 printf("请输入要修改学生的名字:\n"); scanf("%s", (*p).name);//(*p).name:表示先通过指针找到原结构体,然后提取出这个结构体中name的成员printf("请输入要修改学生的年龄:\n"); scanf("%d", &((*p).age)); printf("在method函数中修改完的学生名字:%s 年龄为:%d\n", (*p).name, (*p).age); }
结构体嵌套

什么意思:如果结构体成员的类型是其他结构体,那么就用到了结构体嵌套

struct message { char phone[12]; char mail[100]; }; struct student { char name[20]; int age; char gender; double height;struct message msg;//此时该成员ms的类型是其他结构体的}; //定义变量 struct student stu; //给成员赋值(赋值时也有一点小区别) 1.一个一个赋值strcpy(stu.msg.phone, "12345678910"); strcpy(stu.msg.mail, "123456789@qq.com");2.批量赋值(结构体嵌套,在赋值时也要用大括号嵌套) struct student stu2 = { "徐安琪",18,'F',1.68,{ "12345678910","123456789@qq.com" }};