目录
1_指针理解
1.1变量的值
1.2变量的地址
1.3指针
1.4取变量的地址
2_分析指针
2.1分析指针变量的要素
2.2根据需求定义指针变量
3_指针的使用
3.1指针对变量的读操作
3.2指针对变量的写操作
4_指针占用空间的大小与位移
4.1指针占用空间的大小
4.2指针的位移
5_指针用于传递参数
5.1值传递与地址传递
6_函数与指针
6.1函数指针
6.2指针函数
6.3区分
7_数组与指针
7.1数组的地址
7.2数组元素的指针使用
7.3一道小题目练习一下
7.4传入数组到子函数
7.5字符串与指针
7.6数组指针的使用
8_结构体与指针
8.1结构体指针
9_链表
9.1空间分配的方式
9.2空间动态分配管理函数
9.3链表理解
9.4创建链表
9.5表尾添加节点
9.6表头添加节点
9.7表中添加节点
1_指针理解
1.1变量的值
根据需要的数据类型定义变量,内存会给定义的变量分配空间,就可以这个空间写入值了。
int a = 5; //5就是变量的值
1.2变量的地址
定义变量时,内存会分配对应的空间,且该空间会有地址编号,变量的地址编号值为分配的空间的首字节地址编号值 。
1.3指针
指针是 一种数据类型,用指针类型定义的变量称为指针类型变量(或称指针变量、指针)
指针变量是用来存储变量地址编号值的。
1.4取变量的地址
在C中可用 ’&‘来取变量的地址,格式如下:
&变量名
int a = 5; //5就是变量的值
int *p = &a; //定义一个指针变量p来存储a的地址
2_分析指针
2.1分析指针变量的要素
指针变量本质是一个变量,只不过这种变量存储的内容是变量的地址编号值。
分析指针变量的三要素:
- 变量名
- 指针的类型
- 指向的对象类型
(这些例子简单一眼就能看出来,但后面的数组指针,函数指针,结构体指针就不一定了,不过方法都是一样的)
int *p
变量名 :p
指针类型 :int * (除了变量名以外的内容都是)
指向对象类型:int (除了 *变量名以外的内容就是)
float *q
变量名 :q
指针类型 :float *
指向对象类型:float
int (*p)[20]
变量名 :p
指针类型 :int(*) [20]
指向对象类型:int [20]
int **p
变量名 :p
指针类型 :int **
指向对象类型:int *
2.2根据需求定义指针变量
格式:
指向对象类型 *变量名
先确定指针指向的变量的类型,然后再定义。
int a; int *p; p = &a; //把a的地址存储到变量p中(指针变量p指向了变量a)
float b; float *p; p = &b; //把b的地址存储到变量p中(指针变量p指向了变量b)
char c; char *p; p = &c;
int *m; int **p; p = &m;
指针变量p 存储了变量a的地址;== 也可以说指针变量p指向了变量a;
3_指针的使用
3.1指针对变量的读操作
#include<stdio.h>int main()
{int *p; //定义一个可以储存int类型的指针变量pint a = 10; //定义一个整型变量a,并给a负值10p = &a; //将a的地址储存到变量p中(指针变量p指向了变量a)printf("p :%d\n",p); //p储存了a的地址值printf("&a :%d\n",&a); //可以看到直接打印出a的地址值与打印指针变量p的值是一样的printf("*p :%d\n",*p); //打印出指针变量p指向的变量a的值printf("a :%d\n",a); //可以看到打印指针变量p指向的变量a的值与直接打印出a的值相同return 0;
}
注意:
int *p; 在定义语句中,* 可以理解为指针变量的标志
printf("*p :%d\n",*p); 中的* 是取内容符号
3.2指针对变量的写操作
#include<stdio.h>int main()
{int *p; //定义一个可以储存int类型的指针变量pint a = 10; //定义一个整型变量a,并给a负值10p = &a; //将a的地址储存到变量p中(指针变量p指向了变量a)printf("赋值前\na: %d\n*p: %d\n",a,*p); //未赋值前a和*p都为10*p = 20; //将20赋值给*p.也就是将20赋值给aprintf("赋值后\na: %d\n*p: %d\n",a,*p); //可以看到a和*p都变成了20return 0;
}
4_指针占用空间的大小与位移
4.1指针占用空间的大小
关键字:sizeof
功能:计算对应类型的变量占用空间的大小(字节)
格式:sizeof(变量类型或变量名)
指针变量占用空间的大小与指向对象类型没有关系
#include<stdio.h>int main()
{printf("%d\n",sizeof(int*));printf("%d\n",sizeof(int*[20])); //(指针数组)相当与20个int*占用的空间大小printf("%d\n",sizeof(int(*)[20]));//(数组指针)return 0;
}
指针变量占用的空间大小:
指针变量占用空间4byte(32位平台)
指针变量占用空间8byte(64位平台)
4.2指针的位移
指针位移就是指针变量增减
指针变量的位移与指向对象类型有关
!!!此段代码仅供举例,在实际操作中最好不要将不同类型的指针变量相互赋值!!!
!!!在这里因为指针变量的大小都一样,所以强制赋值没有问题!!!
#include<stdio.h>int main()
{int a = 0x12345678; // 定义一个整数变量 a,并初始化为 0x12345678int *p; // 定义一个 int 类型的指针 pchar *q; // 定义一个 char 类型的指针 qp = &a; // 将指针 p 指向变量 a 的地址q = p; // 将指针 q 指向指针 p 所指向的地址(即 a 的地址)printf("位移前\n");printf("p: %d q: %d\n", p, q); // 输出指针 p 和 q 的值printf("*p: 0x%x *q: 0x%x\n", *p, *q);p += 1; // 将指针 p 向后移动 1 个 int 类型的大小q += 1; // 将指针 q 向后移动 1 个 char 类型的大小printf("位移后\n");printf("p: %d q: %d\n", p, q); // 输出指针 p 和 q 的位移后的值printf("*p: 0x%x *q: 0x%x\n", *p, *q);return 0;
}
通过结果可以看出
p的地址编号偏移了4,q的地址编号偏移了1。看图:
总结:指针的移位跟指向对象的数据类型有关
int *p
指针跳动一步,指针变量里存储的 地址编号就偏移4 地址编号+4
char *p
指针跳动一步,指针变量里存储的 地址编号就偏移1 地址编号+1
short *p
指针跳动一步,指针变量里存储的 地址编号就偏移2 地址编号+2
double *p
指针跳动一步,指针变量里存储的 地址编号就偏移8 地址编号+8
补充:(指针位移的数组应用的很广泛,因为数组元素的地址是连续的,在其他变量的用处实际不大)
p+1; //p储存的地址不变(p = p + 1;//这样p储存的地址才会改变)
p++;//p储存的地址改变
5_指针用于传递参数
5.1值传递与地址传递
值传递是将实参的值传递给函数的参数,在调用函数时,会对实参的值拷贝一份副本,程序只会在函数内部对形参进行操作,不会对原始变量(实参)进行修改。
#include<stdio.h>// 声明函数 mm,接受两个参数:一个 int 类型和一个 char 类型
void mm(int a, char c);int main(void)
{int x = 10; char y = 'A'; mm(x, y); // 调用 mm 函数,传递 x 和 y 的值(值传递)// 打印 x 的值,由于值传递,x 的值在 mm 函数中没有变化,仍然是 10printf("x:%d\n", x); // 打印 y 的值,由于值传递,y 的值在 mm 函数中没有变化,仍然是 'A'printf("y:%c\n", y);return 0; // 返回 0,表示程序正常结束
}// 定义 mm 函数,接受两个参数:一个 int 类型的 a 和一个 char 类型的 c
void mm(int a, char c)
{// 在函数内部,a 被修改为原来 a 的值加 1,即 10 + 1 = 11a = a + 1; // 在函数内部,c 被修改为原来 c 的值加 1,即 'A' 的 ASCII 值为 65,加 1 后变为 66,对应字符 'B'c = c + 1;// 打印修改后的 a 和 c 的值printf("a:%d\n", a); // 输出 11,因为 a 被修改为 11printf("c:%c\n", c); // 输出 'B',因为 c 被修改为 'B'
}
地址传递是将实参的地址(指针)传递给参数。在这种方式,函数的参数实际上指向了实参的地址,在调用函数时,会对原始变量(实参)进行操作。
#include<stdio.h>
void mm(int *a, char *c);int main(void)
{int x = 10;char y = 'A';mm(&x, &y); // 传递变量的地址printf("x: %d\n", x); // 这里的 x 会被修改为 11printf("y: %c\n", y); // 这里的 y 会被修改为 'B'return 0;
}void mm(int *a, char *c)
{*a = *a + 1; // 修改 a 指针指向的值*c = *c + 1; // 修改 c 指针指向的值printf("a: %d\n", *a); // 11printf("c: %c\n", *c); // 'B'
}
地址传递使用场景:
- 想要在函数中改变实参的值。
- 想要获取子函数中的数据(特别是想要多个数据的时候)。ex:
子函数中寻找100~999中的所有水仙花数打印
主函数要水仙花数的个数和总和
/********************************************************************* 水仙花数(Narcissistic Number) 是指一个 n 位数,其每个数字的 n 次方和 等于它本身。 例如,三位数的水仙花数是指,某个三位数的每个数字的立方和等于这个数本身。 **********************************************************************/ #include<stdio.h> int sxh(int *s, int *c);int main(void) {int sum = 0,cont = 0;sxh(&sum,&cont);printf("水仙花数的和为:%d\n",sum);printf("水仙花数的个数:%d\n",cont);return 0; }int sxh(int *s, int *c) {int i,ge,shi,bai;printf("水仙花数有:\n");for(i = 100;i <= 999;i++){ge = (i / 1) % 10;shi = (i / 10) % 10;bai = (i / 100) % 10;if(ge*ge*ge + shi*shi*shi + bai*bai*bai == i){printf("%d\n",i);*s += i;(*c)++;}}return 0; }
- 需要传递一个数组到子函数中。(看数组与指针部分)
6_函数与指针
6.1函数指针
指向对象类型是函数的指针叫函数指针,本质是指针。
作用:储存函数的地址变化值(函数的地址编号可以用函数名表示)
int (*f)(char a);
中间(*f)的括号必须加,不加就变成了指针函数,指针函数本质是函数。(后面会区分)
变量名 :f
指针类型 :int (*) (char a) (除了变量名以外的内容都是)
指向对象类型:int (char a) (除了 *变量名以外的内容就是)
作用: 存一个函数的地址,该函数的返回值为int类型且有一个char类型参数。
#include<stdio.h>int mm(char a);
int main(void)
{printf("int(*)(char a)类型的指针占用空间的大小为%dbyte\n",sizeof(int(*)(char a)));int(*f)(char a); //定义一个函数指针f = mm; //将函数的地址赋值给指针变量f//通过函数名调用函数int a = mm('A');printf("'A'的ASCII值为:%d\n",a);//当通过函数指针调用int b = f('B');printf("'B'的ASCII值为:%d\n",b);//通过解引用函数指针调用int c = (*f)('C');printf("'C'的ASCII值为:%d\n",c);return 0;
}int mm(char a)
{printf("进入int (char a)类型的函数\n");printf("函数的功能为打印字符%c并返回其ASCII值\n",a);return a;
}
多举2个例子:
int (*p)(void);
变量名 :p
指针类型 :int (*) (void) (除了变量名以外的内容都是)
指向对象类型:int (void) (除了 *变量名以外的内容就是)
作用: 存一个函数的地址,该函数的返回值为int类型且没有参数。
void (*q)(int a,char *b);
变量名 :q
指针类型 :void (*) (int a,char *b) (除了变量名以外的内容都是)
指向对象类型:void (int a,char *b) (除了 *变量名以外的内容就是)
作用: 存一个函数的地址,该函数无返回值为且有一个int类型和char *类型参数。
函数指针的使用场景:
将一个函数作为另一个函数的参数。
ex:通过接口函数启动功能函数。
#include<stdio.h>void fun(void(*q)(void)); // 声明接口函数
void f1(void); // 声明功能函数1
void f2(void); // 声明功能函数2
void f3(void); // 声明功能函数3
void f4(void); // 声明功能函数4
void f5(void); // 声明功能函数5int main(void)
{fun(f1); // 通过接口函数调用功能函数fun(f2);fun(f3);fun(f4);fun(f5);return 0;
}// 接口函数:接收一个函数指针并调用对应的功能函数
void fun(void(*q)(void))
{(*q)(); // 调用传入的函数
}// 功能函数1
void f1(void)
{printf("进入功能块1\n");
}// 功能函数2
void f2(void)
{printf("进入功能块2\n");
}// 功能函数3
void f3(void)
{printf("进入功能块3\n");
}// 功能函数4
void f4(void)
{printf("进入功能块4\n");
}// 功能函数5
void f5(void)
{printf("进入功能块5\n");
}
更多接口函数例子==》C语言_接口函数
6.2指针函数
指针函数本质是一个函数,一个可以返回地址编号的函数。
int *f(void);
函数名 : f
参数 :无
返回值 :int *
作用 : 该函数的返回值是地址编号,需要定义一个指针变量接收。
指针函数的使用场景:
用于动态分配。
int *malloc(int n);
具体看后面的链表章节
6.3区分
有括号的就是函数指针,没有括号的就是指针函数(类似数组指针和指针数组)。
分析
int (*mm)(void (*f)(float b), int a, char *m);
int *mm(void (*f)(float b), int a, char *m);
int *(*mm)(void (*f)(float b), int a, char *m);
int (*mm)(void (*f)(float b), int a, char *m);
函数指针:
变量名 :mm
指针类型 :int (*)(void(*f)(float b),int a,char *m)
指向对象类型:int (void(*f)(float b),int a,char *m)
这是一个指针变量,指针存储函数的地址,函数的要求如下:
返回值类型 : int
参数 : int a //传入一个整型值
char * m //传入一个字符型变量的地址
void(*f)(float b) //传入一个函数的地址,函数要求如下:
返回值: 无
参数 : float b //传入一个浮点值
#include <stdio.h>// 定义一个简单的函数,接受一个 float 类型的参数
void example_function(float b) {printf("接收到的 float 值: %f\n", b);
}// 定义一个函数,符合 mm 的签名,接收一个函数指针、一个整数和一个字符串
int my_function(void (*f)(float b), int a, char *m) {printf("整数: %d, 字符串: %s\n", a, m);f(3.14); // 调用传入的函数 freturn a * 2;
}int main() {// 定义函数指针 mm,指向 my_functionint (*mm)(void (*f)(float b), int a, char *m) = my_function;// 通过 mm 调用 my_function,并传入 example_function、整数 5 和字符串 "Hello, World!"int result = mm(example_function, 5, "Hello, World!");printf("结果: %d\n", result);return 0;
}
int *mm(void (*f)(float b), int a, char *m);
指针函数:
函数名 : mm
返回值 : int *
参数 : int a //传入一个整型值
char * m //传入一个字符型变量的地址
void(*f)(float b) //传入一个函数的地址,函数要求如下:
返回值: 无
参数 : float b //传入一个浮点值
#include <stdio.h>
#include <stdlib.h>// 这是一个符合要求的函数,接收一个 float 类型的参数,并返回 void
void example_function(float b) {printf("函数 example_function 被调用,参数为: %f\n", b);
}// mm 函数,返回一个 int* 指针
int* mm(void (*f)(float b), int a, char *m) {// 打印传入的整数和字符串printf("传入的整数 a: %d\n", a);printf("传入的字符串 m: %s\n", m);// 调用传入的函数 f,传入一个 float 参数f(3.14);// 使用 malloc 分配内存int *result = (int*)malloc(sizeof(int));if (result != NULL) {*result = a * 2; // 计算并存储结果}return result; // 返回指向结果的指针
}int main() {// 定义一个函数指针 f,指向 example_functionvoid (*f_ptr)(float) = example_function;// 调用 mm 函数,传入函数指针 f_ptr,整数 5 和字符串 "Hello"int *result = mm(f_ptr, 5, "Hello");// 打印 mm 函数返回的 int* 指针值和指针解引用后的值if (result != NULL) {printf("计算结果的指针地址: %p\n", (void*)result);printf("解引用后的结果: %d\n", *result);// 使用完 malloc 分配的内存后,记得释放它free(result);}return 0;
}
int *(*mm)(void (*f)(float b), int a, char *m);
函数指针:
变量名 :mm
指针类型 :int *(*)(void(*f)(float b),int a,char *m)
指向对象类型:int * (void(*f)(float b),int a,char *m)
这是一个指针变量,指针存储函数的地址,函数的要求如下:
返回值类型 : int *
参数 : int a //传入一个整型值
char * m //传入一个字符型变量的地址
void(*f)(float b) //传入一个函数的地址,函数要求如下:
返回值: 无
参数 : float b //传入一个浮点值
#include <stdio.h>
#include <stdlib.h>// 这是一个符合要求的函数,接收一个 float 类型的参数,并返回 void
void example_function(float b) {printf("函数 example_function 被调用,参数为: %f\n", b);
}// mm 函数的实现,符合声明
int *mm(void (*f)(float b), int a, char *m) {// 打印传入的整数和字符串printf("传入的整数 a: %d\n", a);printf("传入的字符串 m: %s\n", m);// 调用传入的函数 f,传入一个 float 参数f(3.14);// 计算 a * 2,并返回其地址int *result = (int *)malloc(sizeof(int)); // 动态分配内存if (result != NULL) {*result = a * 2;}return result;
}int main() {// 定义一个函数指针 f,指向 example_functionvoid (*f_ptr)(float) = example_function;// 定义一个函数指针 mm,指向 mm 函数int *(*mm_ptr)(void (*f)(float b), int a, char *m) = mm;// 调用 mm 函数,传入函数指针 f_ptr,整数 5 和字符串 "Hello"int *result = mm_ptr(f_ptr, 5, "Hello");// 打印 mm 函数返回的 int* 指针值和指针解引用后的值if (result != NULL) {printf("计算结果的指针地址: %p\n", (void*)result);printf("解引用后的结果: %d\n", *result);// 使用完 malloc 分配的内存后,记得释放它free(result);}return 0;
}
7_数组与指针
7.1数组的地址
!!!数组的首元素地址属性和数组的地址属性不一样!!!
数组的首元素地址:
0号元素的地址属性表示首个元素的地址
数组名代表数组首元素地址(或 &a[0])
偏移:a+1;偏移一个数组元素的长度地址
数组的地址:
数组的地址的属性表示整个数组的地址:&a
数组的首元素地址编号值和数组的地址编号值一样,但偏移量不一样
偏移:&a+1;偏移一个数组的长度地址,即 偏移量 == 元素个数 * 元素类型大小
7.2数组元素的指针使用
由于数组名可以代码数组首元素地址,所以通过:
(数组名+i)的形式表示数组的i号元素的地址
*(数组名+i)的形式来获取数组的i号元素的值
注意:
数组名只能代表数组首元素的地址,不能代表其他元素的地址
所以用数组名访问的时候,不能用 数组名++ 的形式
也可以通过指针变量来访问数组的元素:
通过指针操作数组中的元素,要先定义一个可以指向数组元素的指针,
然后,
通过p++的形式访问某个元素的地址 //指针变量p存储的地址是变化的
通过*p的形式访问某个元素的内容 //要注意指针某一时刻存了谁的地址
也可以
通过p+i的形式访问某个元素的地址 //指针变量p存的地址不变,一直是首元素地址
通过*(p+i)的形式访问某个元素的内容
7.3一道小题目练习一下
如果定义:
char a,b,c,d,e,f,x,y;
char niu[6];
niu[0] = 3;
niu[1] = 6;
niu[2] = 10;
niu[3] = 21;
niu[4] = 40;
niu[5] = 50;
char *sp = niu;求:
a=*sp;
b=*sp+1;
c=*sp++;
d=*sp;
e=*(sp+1);
f=*sp;
x = sizeof(niu[6]);
y = sizeof(niu);
z = sizeof(char[6]);
k = sizeof(sp);的结果(注意假设程序从上往下执行):
a= b= c= d=
e= f= x= y=
z= k=
#include<stdio.h>int main(void)
{char a,b,c,d,e,f,x,y,z,k;char niu[6];niu[0] = 3;niu[1] = 6;niu[2] = 10;niu[3] = 21;niu[4] = 40;niu[5] = 50;char *sp = niu;a = *sp; //sp指向niu[0] a = niu[0] = 3b = *sp+1; //sp指向niu[0] b = niu[0]+1 = 4c = *sp++; //sp指向niu[0] c = niu[0] = 3 (*sp++ == *(sp++) sp++运算符是先赋值后自增,所以本次赋值在自增前)d = *sp; //sp指向niu[1] d = niu[1] = 6 (上一行代码进行了自增)e = *(sp+1); //sp指向niu[1],但(sp+1)的地址为niu[2] e = niu[2] = 10f = *sp; //sp指向niu[1] f = niu[1] = 6x = sizeof(niu[6]);y = sizeof(niu);z = sizeof(char[6]);k = sizeof(sp);printf("a = %d\n",a);printf("b = %d\n",b);printf("c = %d\n",c);printf("d = %d\n",d);printf("e = %d\n",e);printf("f = %d\n",f);printf("x = %d\n",x);printf("y = %d\n",y);printf("z = %d\n",z);printf("k = %d\n",k);return 0;
}
思考:如果把代码中所以char改为int,指针的偏移有什么变换?(看4.2指针的位移)
7.4传入数组到子函数
数组的空间特点:元素空间分配连续
基于数组的空间特点,我们可以吧数组的首元素地址传给子函数(子函数定义一个指针变量的形参来接收数组首元素地址),子函数就可以通过地址偏移的方式访问所以数组元素。
1. 用户在主函数中定义一个数组,往数组中输入10个数据
写一个子函数,统计用户输入的数据非负数的个数打印
并且求非负数的和返回给主函数在主函数中打印。
#include <stdio.h>int Sub(int *p, int n);int main()
{int a[10]; // 存储用户输入的 10 个数int value; // 存储非负数的和printf("请输入10个数:\n");// 输入 10 个整数for (int i = 0; i < 10; i++){scanf("%d", &a[i]);}// 计算并返回非负数的和value = Sub(a, 10);// 输出非负数的和printf("非负数的和为:%d", value);return 0;
}// 计算非负数的和
int Sub(int *p, int n)
{int sum = 0; // 初始化和为 0// 输出非负数printf("非负数有:\n");// 遍历数组中的所有元素for (int i = 0; i < n; i++) // 使用传入的数组大小 n{if (*(p + i) >= 0) // 判断当前元素是否为非负数{printf("%d\n", *(p + i)); // 打印当前的非负数sum += *(p + i); // 将非负数累加到 sum}}return sum; // 返回非负数的和
}
2. 用户在主函数中输入数组后
在子函数中去掉最大最小求平均值
返回平均值在主函数中打印
插个知识点:
冒泡排序
作用:将数组中的数据进行从大到小或者从小到大排序
原理:
int a[6] = { 68 , 100 , 90 , 34 , 200 , 60};
说明:轮数循环从1开始,每轮比较的次数 j == 数据个数 n - 轮数 i
每轮比较的次数从0开始,因为要用这个循环变量当数组的下标。
#include <stdio.h>float Average(int *p, int n);int main()
{int a[10]; float average; printf("请输入10个数:\n");for (int i = 0; i < 10; i++){scanf("%d", &a[i]); /}average = Average(a, 10);printf("去掉最大最小后的平均值为%.2f\n", average);return 0;
}// 计算去掉最大最小数后的平均值的函数定义
float Average(int *p, int n)
{int temp, sum = 0; // 临时变量 temp 用于交换,sum 初始化为 0,用来累加去掉最大最小数后的和float aver; // 存储计算出的平均值// 冒泡排序,按升序排列数组for (int i = 1; i < n; i++) // 外层循环:每次将最大的数移动到末尾{for (int j = 0; j < n - i; j++) // 内层循环:比较相邻的两个数,较大的数交换到后面{if (p[j] > p[j + 1]) // 如果当前数比下一个数大,则交换{temp = p[j]; // 保存当前数p[j] = p[j + 1]; // 将下一个数赋值给当前数p[j + 1] = temp; // 将保存的当前数赋值给下一个数}}}// 去掉最大值和最小值后,计算剩余部分的和for (int k = 1; k < n - 1; k++) // 从第二个元素开始,到倒数第二个元素{sum += p[k]; // 累加每个元素到 sum}// 计算去掉最大最小数后的平均值aver = (float)sum / (n - 2); // 计算平均值,确保进行浮点数运算return aver; // 返回计算出的平均值
}
在上面2段代码中,关于在子函数调用数组我用了2中不同的形式:
用指针的形式:*(p+i)
用数组的形式:p[i]
虽然形式参数的类型是指针,但这两种方式是等价的,*(p + i)
等价于 p[i],
想想在用数组的时候是不是也用过指针的形式调用数组。
7.5字符串与指针
字符串其实是数组,用指针操作字符串其实就是用指针操作数组,在这不讲太多,可以看下这C_字符串其实就是字符数组
也可以看看下一节关于数组指针操作字符的二维数组的部分。
7.6数组指针的使用
区分:
数组指针: char (*p)[10]; 这是一个指针,可以存一个char [10]类型的数组的地址编号
指针数组: int *p[10]; 这是一个数组,可以存10个int * 类型的指针变量
有括号为指针,无括号为数组(跟函数指针和指针函数类似)
数组指针存了数组的地址编号,意味着整体操作数组,这多很多类型的数组没有操作价值,但可以操作字符数组,也就是操作字符串,一般用在字符的二维数组。
接下来讲讲数组指针操作字符的二维数组:
①定义一个数组指针
char (*p)[10];
变量名 :p
指针类型 :char *[10]
指向对象类型:char [10]
②明确指向
假如有一个字符的二维数组:
char a[10][10];
p = a;
③使用
p++; //指针指向改变
p+1; //指针指向不变
练习:
主函数有一个指令包,指令包里有10个字符串指令,
用户再输入一个字符串指令,
写一个子函数,判断用户输入的字符串指令是否在指令包中,
如果在指令包中返回1,不在指令包中返回0。
分析:
10个指令存在一个二维数组中
主函数:
指令包二维数组
用户输入指令字符串
子函数:
参数:char (*p)[10], char *m
返回值: int
说明:
和二维数组中的每个字符串进行对比,对比成功返回1,失败返回0
#include <stdio.h>
#include <string.h>int Judge(char (*p)[10],char *i);int main()
{int judge;char package[10][10] = {"123456","abcd","98765","55555","4444","333","22","liao","jia","tong"};char ins[10];printf("请输入指令:");scanf("%s",ins);judge = Judge(package,ins);if(judge == 1){printf("输入正确\n");}else if(judge == 0){printf("输入错误\n");}return 0;
}/*******************************************************
函数名 : Judge
函数功能 : 判断指令是否在指令包中
函数参数 : char (*p)[10], char *i
函数返回值 : int
函数描述 : 如果字符串i在字符串数组p中,则返回1,不在则返回0
*******************************************************/int Judge(char (*p)[10], char *i)
{for (int j = 0; j < 10; j++) {if (strcmp(p[j], i) == 0) { // 如果匹配return 1; // 返回 1 表示输入正确}}return 0; // 如果没有匹配,返回 0
}//int Judge(char (*p)[10],char *i)
//{
// int j = 0;
// while(*(p+j) != NULL)
// {
// if(strcmp((char *)(p+j),i)) == 0)
// {
// return 1;
// }
// j++;
// }
// return 0;
//}
8_结构体与指针
关于结构体的基础知识在这里不讲,有需要可以看这==》C_结构体
8.1结构体指针
假如已经声明了一个结构体:
typedef struct book
{
char title[50]; // 书名
char author[50]; // 作者
char id[50]; // 书籍编号
float pop; // 热度
int stock; // 库存
float price; // 价格
} BOK; // 结构体类型的别名 BOK
定义一个结构体指针:
BOK *f;
变量名 :f
指针类型 :BOK *
指向对象类型:BOK
使用:
明确指向:
BOK bk1;
f = bk1;
格式:
这个符号: -> 是结构体指针特有的,是结构体元素到结构体具体成员的指向。
结构体指针变量名->成员变量名;
f->title f->price
#include <stdio.h>
#include <string.h>
typedef struct book
{char title[50]; // 书名char author[50]; // 作者char id[50]; // 书籍编号float pop; // 热度int stock; // 库存float price; // 价格
} BOK; // 结构体类型的别名 BOKint main()
{BOK bk1 ={"C Programming", // title"Dennis Ritchie", // author"001", // id4.5, // pop10, // stock39.99 // price};BOK *f;f = &bk1;f -> price = 29.99;strcpy(f -> title , "C语言");printf("Book 1: %s by %s\n ID: %s\n Popularity: %.2f\n Stock: %d\n Price: %.2f\n",f->title, f->author, f->id, f->pop, f->stock, f->price);return 0;
}
9_链表
9.1空间分配的方式
自动分配:
系统根据用户定义的变量来分配空间,分配的位置为栈区。
访问可以通过变量访问,也可以通过地址访问。
动态分配:
用户通过动态分配函数人为申请空间,人为释放空间申请到的空间在堆区。
分配到的空间没有名字,只能通过地址访问。
9.2空间动态分配管理函数
malloc函数:
函数原型:
#include <stdlib.h> //头文件
void *malloc(unsigned int size); //函数
函数名 : malloc
函数参数 :unsigned int size
函数返回值 :void *
说明 :此函数有1个整型参数size,这个参数是用来请求分配的内存块大小的,
单位是字节(byte),此函数会返回一个地址编号,所在地址中存的数据类型 不确定。
功能 : 在堆区申请一块size字节的空间
会把申请到的空间的地址的首字节编号返回,如果分配失败则返回NULL
此空间没有类型(可以强转成任何地址类型)
注意 :申请空间是为了存数据
申请到的空间要类型转换
返回的是申请到的空间的首字节地址
#include <stdio.h>
#include <stdlib.h>int main() {// 请求分配一个整数大小的内存int* ptr = (int*)malloc(sizeof(int));if (ptr == NULL) {printf("内存分配失败\n");return 1;}// 使用分配的内存*ptr = 10;printf("存储的值是: %d\n", *ptr);// 释放分配的内存free(ptr);return 0;
}
free函数:
函数原型:
#include <stdlib.h> //头文件
void free(void *prt); //函数
函数名 :free
函数参数 :void *prt
函数返回值 :无
功能 : 释放指针指向的堆区空间的内容
权限自由
注意 :释放的是空间里的内容,不是空间
调用后,*prt的值发生了变化,因为这块空间里的内容已经释放掉了,但prt还是
指向这块空间的首地址,不太好,所以最好:
prt = NULL; //避免”野指针“
#include <stdio.h>
#include <stdlib.h>int main() {// 动态分配内存int* ptr = (int*)malloc(sizeof(int));if (ptr == NULL) {printf("内存分配失败\n");return 1;}// 使用分配的内存*ptr = 42;printf("存储的值是: %d\n", *ptr);// 释放分配的内存free(ptr);// 注意:释放后,不要再使用该指针ptr = NULL; // 设为NULL,避免悬空指针问题return 0;
}
calloc函数:
#include <stdlib.h> //头文件
void *calloc(unsigned int num,unsigned int size); //函数
函数名 : calloc
函数参数 :unsigned int num,unsigned int size
函数返回值 :void *
说明 :此函数有2个整型参数 num和size,num是要申请的内存块数,size是用来请求分 配的每块内存块大小的,单位是字节(byte)。
功能 : 在堆区申请num块size字节的空间
会把申请到的空间的地址的首字节编号返回,如果分配失败则返回NULL
此空间没有类型(可以强转成任何地址类型)
#include <stdio.h>
#include <stdlib.h>int main() {// 分配并初始化 10 个整数大小的内存块int* ptr = (int*)calloc(10, sizeof(int));if (ptr == NULL) {printf("内存分配失败\n");return 1;}// 打印每个元素的值(应为 0,因为 calloc 初始化了内存)for (int i = 0; i < 10; i++) {printf("ptr[%d] = %d\n", i, ptr[i]);}// 释放分配的内存free(ptr);return 0;
}
calloc和malloc的区别主要是以下两点:
1.calloc是申请num块字节数为size的内存空间。
malloc是申请1块字节数为size的内存空间
calloc(6,4); 等价于malloc(24); //在堆区申请24字节的空间
2.malloc不会初始化申请到的空间,而calloc会初始化申请到的空间。
9.3链表理解
还没写b( ̄▽ ̄)d