目录
3.sizeof 、 strlen、memset、mencpy 的使用
4.4strncmp 函数用于比较两个字符串的前 n 个字符
写这篇文章主要是用来总结下指针的内容,加深自己的印象,能力有限讲解的不是很清楚
一、指针变量
1.概念
指针变量 == 存放地址的变量
int a = 10; //定义一个变量
int *p; //定义一个指针变量,*是一个标识符告诉系统这是一个指针变量,用来存放别人的地址
// * 标识作用,只产生在指针变量声明的时候,其他地方都是运算作用
p = &a; //把a的地址赋给指针变量
printf("%d\n",a);
printf("%d\n",*p);//这里的*是取地址的意思, *p 相当于 *(&a) 相当于 a,通过a的地址来访问a
2.指针类型
定义指针时候要求类型一致
下面程序定义了一个 int变量,分别用 int 型指针和 char 型指针指向它,我们可以看到他们输出的地址是一致的因为都指向同一个地址,但是 char 指针输出的值却出现了错误,是因为类型不同他们的跨度不同所造成的
从输出结果可以看到 :
int 跨度从 0C——>10 为4个字节
char 跨度从0C——>0D 为1个字节
#include <stdio.h>
int main()
{
int a = 1000;
int *p = &a;
char *d = &a;
printf("输出地址:\n%p\n%p\n", p, d);
printf("输出值%d\n", *p);
printf("输出值%d\n", *d);
printf("++p = %p\n", ++p);
printf("++d = %p\n", ++d);
return 0;
}
输出地址:
000000000061FE0C
000000000061FE0C
输出值1000
输出值-24
++p = 000000000061FE10
++d = 000000000061FE0D
3.为什么需要用到指针
举例1
这里举一个简单的例子来说明
#include <stdio.h>
void change1(int data1, int data2)
{
int temp;
temp = data1;
data1 = data2;
data2 = temp;
}
void change2(int *data1, int *data2)
{
int temp;
temp = *data1;
*data1 = *data2;
*data2 = temp;
}
int main(void)
{
int a1 = 5;
int b1 = 10;
change1(a, b); //未使用指针
printf("未使用指针:a = %d b = %d\n", a, b);
int a2 = 5;
int b2 = 10;
change2(&a, &b); //使用指针,需要输入地址
printf("使用指针:a = %d b = %d\n", a, b);
return 0;
}
未使用指针:a1 = 5 b1 = 10
使用指针:a2 = 10 b2 = 5
这里可以看到,我们封装了两个交换数值的函数,一个使用了指针一个未使用,从输出结果可以看到,调用未使用指针的函数,两个数的值是不会经行交换的
未使用指针:虽然传入的值是一样的,但是main函数里值的地址和change1函数中值的地址是不同的,交换只是在change1函数中开辟了一个内存空间经行执行,改变了局部变量,所以在main函数里调用不起作用,但是将 a 和 b 定义为全局变量,那么在main函数中也是可以生效的
使用指针:直接将地址传过来通过地址来间接访问,所以在change2函数中可以直接访问到main函数里的内存空间再经行交换,是有效的交换
举例2
带返回值的应用
int add1(int num)
{
return ++num;
}
int add2(int *num)
{
return ++(*num);
}
int main(void)
{
int num1 = 10; //未使用指针调用
int re1; //接收返回值
int num2 = 10; //使用指针调用
int re2; //接收返回值
re1 = add1(num1); //未使用指针
re2 = add2(&num2); //使用指针
printf("未使用指针num1 = %d,返回值re1 = %d\n", num1, re1);
printf("使用指针num2 = %d,返回值re2 = %d\n", num2, re2);
return 0;
}
未使用指针num1 = 10,返回值re1 = 11
使用指针num2 = 11,返回值re2 = 11
可以看到不使用指针返回的时候只起一次作用,num1的值不会改变,下次调用返回值依然不会变
使用指针后num2的值发生了改变,下次调用函数返回值会继续加1
举例3
在单片机寄存器中使用,指向一个内存地址
unsigner int *p = (unsigner int *)0x00000000FED
二、指针和数组
1.概念
可以用一个指针变量指向一个数组元素
int a[5] = {1,2,3,4,5};
int *p;
p = &a[0];
数组名 a 代表数组中首个元素 a[0] 的地址因此下面两个语句等价
p = &a[0];
p = a;
2.指针增量和数组的关系
遍历数组
#include <stdio.h>
int main(void)
{
int arr[5] = {1, 2, 3, 4, 5};
int *p;
p = arr;
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("地址:%p,值:%d\n", p + i,*(p + i));
printf("直接使用数组名-地址:%p,值:%d\n", arr + i, *(arr + i));
printf("\n");
}
return 0;
}
使用指针来输出-地址:000000000061FDF0,值:1
直接使用数组名-地址:000000000061FDF0,值:1
使用指针来输出-地址:000000000061FDF4,值:2
直接使用数组名-地址:000000000061FDF4,值:2
使用指针来输出-地址:000000000061FDF8,值:3
直接使用数组名-地址:000000000061FDF8,值:3
这里指针 p+i 表示指针地址偏移 i 个 int 的跨度,也就是指向数组里的第 i 个数
通过指针来访问效率大于用下标来访问数组元素
指针和数组名的区别
1)
printf("%d\n", *p++); 是可以通过的,表示指针变量,可以改变
printf("%d\n", *arr++); 编译错误,是一个指针常量,不能被改变
2)
printf("%d\n", sizeof(p));——输出8,操作系统用8个字节表示一个地址
printf("%d\n", sizeof(arr));——输出20,数组里有5个数,4*5 = 20
3.函数指针数组结合使用
这种方法在开发过程中经常会遇到,常见的情况比如:串口发送一串数据data出去,需要发送的数据长度为len
#include <stdio.h>
void initArr(int *data, int len) //这里len不需要用指针传入,len只是作为循环使用不需要改变
{
int i;
for (i = 0; i < len; i++)
{
printf("输入第%d个数据:\n", i + 1);
scanf("%d", data); //这里可以理解为 &(*data) 所以等同于 data
data++;
}
}
void printArr(int *data, int len)
{
int i;
for (i = 0; i < len; i++)
{
printf("%d", *data);//data为指针,指向main函数arry[0]的地址,*data表示取arry[0]的数值
data++; //指向下移
}
}
int main(void)
{
int arry[5];
int len = sizeof(arry) / sizeof(arry[0]);
initArr(arry, len);
printArr(arry, len);
}
输入第1个数据:
5
输入第2个数据:
6
输入第3个数据:
6
输入第4个数据:
5
输入第5个数据:
8
56658
4.二维数组
定义一个3行4列的二维数组
int a[3][4] = { { 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9,10,11,12}};
a表示第一行的首地址
a+1指向第二行首地址,跨度为一整行,这里就是1,2,3,4加起来16个字节
a+2指向第三行首地址
a[0] ——> 1,2,3,4
a[0]表示列首地址指向1,这里相对于一维数组把a[0]整个看作数组名,a[0]+1指向2,跨度为4个字节
a[1] ——> 5,6,7,8
a[2] ——> 9,10,11,12
5.数组指针
数组指针是一个指针
#include <stdio.h>
int getData(int (*p)[4], int hang, int lie)
{
int data;
data = *(*(p + hang) + lie);
return data;
}
void inputHL(int *hang, int *lie)
{
printf("输入行和列:\n");
scanf("%d%d", hang, lie);
printf("\n");
}
int main(void)
{
int arr[3][4] = {{11, 22, 33, 44},
{55, 66, 77, 88},
{99, 10, 11, 12}};
int hang, lie;
int data;
inputHL(&hang, &lie);
data = getData(arr, hang, lie);
printf("%d行%d列的值是:%d\n", hang, lie, data);
}
输入行和列:
0
3
0行3列的值是:44
这里我们来讲一下 data = *(*(p + hang) + lie)
我们输入hang为1 ,lie 为 1
p指向的第一行首地址
p + 1指向第二行首地址
*(p + 1)对其经行解引用指向第二行第一个元素的首地址,也就是列的首地址
*(p + 1) + 1表示列加一个跨度,指向第二行第二列元素的地址
*(*(p + 1) + 1)最后解引用,得到第二行第二列元素的值
6.指针数组
6.1定义
一个数组,若其元素均为指针类型数据,称为指针数组
指针数组中的每一个元素都存放一个地址,相当于一个指针变量
下标 | 0 | 1 | 2 | 3 | 4 |
元素 | int * | int * | int * | int * | int * |
int *p[4];
6.2举例
#include <stdio.h>
int main(void)
{
int i;
//定义一个指针数组
char *p[4] = {
"12345",
"67890",
"abcde",
"fghijk"};
for (i = 0; i < 4; i++)
{
printf("%s\n", p[i]);
}
return 0;
}
12345
67890
abcde
fghijk
7.二级指针
#include <stdio.h>
int main(void)
{
int data = 100;
int *p = &data;
printf("data的地址时:%p\n", &data);
printf("p保存data的地址:%p,内容是%d\n", p, *p);
int **p2;
p2 = &p;
printf("p2保存p的地址:%p\n", p2);
printf("*p2是:%p\n", *p2);
printf("**p2来访问data:%d\n", **p2);
return 0;
}
data的地址时:000000000061FE14
p保存data的地址:000000000061FE14,内容是100
p2保存p的地址:000000000061FE08
*p2是:000000000061FE14
**p2来访问data:100
8.二级指针和二维数组
三、指针和函数
1.函数指针
1.1 定义:
1)如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针
2)函数名就是地址
int change(int a,int b); //定义一个函数
int (*p)(int a,int b); //定义一个函数指针,类型要和函数一致
1.2 举例
下面举个简单的例子来验证下
#include <stdio.h>
void changenum(int *a, int *b)
{
int c;
printf("hello world\n");
c = *a;
*a = *b;
*b = c;
}
int main()
{
int one, two;
one = 5;
two = 10;
void (*p)(int *a, int *b); //定义一个函数指针变量,类型和函数保持一致
p = changenum; //指向函数,函数名就是函数的地址
(*p)(&one, &two); //调用函数指针,间接访问函数
printf("交换后one = %d,two = %d\n", one, two);
return 0;
}
hello world
交换后one = 10,two = 5
可以和指针变量做对比,可以很好的理解
指针变量 | 函数指针 |
int a = 10;//定义一个变量 | void change(int a);//定义一个函数 |
int *p ; //定义一个int指针, p = &a; //指向a的地址 printf("%d",*p);//通过指针间接访问 a | void (*p)(int a); //定义一个函数指针 p = change; //指向函数的地址 (*p)(); //通过指针间接访问函数 |
1.3函数指针作为入口参数
这里举一个例子,实现内容为:输入两个值num1 num2,再输入一个控制命令cmd,输入1将取出最大值,输入2取最小值,输入3求和
#include <stdio.h>
//返回两数最大值
int getMax(int num1, int num2)
{
int re;
re = num1 > num2 ? num1 : num2;
return re;
}
//返回两数最小值
int getMin(int num1, int num2)
{
int re;
re = num1 < num2 ? num1 : num2;
return re;
}
//返回两数和
int getAdd(int num1, int num2)
{
int re;
re = num1 + num2;
return re;
}
int result(int num1, int num2, int (*p)(int, int)) //函数指针做参数传入
{
int re; //用于接收返回的结果
re = (*p)(num1, num2); //调用函数指针操作两个数
return re; //返回结果
}
int main(void)
{
int a;
int b;
int cmd;
int ret;
int (*p)(int, int); //定义一个函数指针
printf("输入两个整数:\n");
scanf("%d,%d", &a, &b);
printf("选择要做的操作:\n1取最大值\n2取最小值\n3求和\n");
scanf("%d", &cmd);
switch (cmd)
{
case 1:
p = getMax; //函数指针指向取最大值函数
break;
case 2:
p = getMin; //函数指针指向取最小值函数
break;
case 3:
p = getAdd;
break;
default:
break;
}
ret = result(a, b, p); //入口参数为指针
printf("返回结果为:%d\n", ret);
}
输入两个整数:
12,13
选择要做的操作:
1取最大值
2取最小值
3求和
3
返回结果为:25
1.4函数指针作为返回值
四、字符串部分
1.定义
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a[3] = {11, 22, 33}; //定义一个数组
char c = 'a'; //定义一个字符
char str[5] = {'a', 'b', 'c', 'd', 'e'}; //定义一个字符串数组
printf("——1——\n");
//遍历字符串数组
for (int i = 0; i < sizeof(str) / sizeof(str[0]); i++)
{
printf("%c", str[i]);
}
printf("\n——2——\n");
char str2[5] = "joker"; //定义一个字符串数组
for (int j = 0; j < sizeof(str2) / sizeof(str2[0]); j++)
{
printf("%c", str2[j]);
}
printf("\n——3——\n");
char str3[] = "jokersjwkjad"; //数组大小不写
for (int j = 0; j < sizeof(str3) / sizeof(str3[0]); j++)
{
printf("%c", str3[j]);
}
printf("\n——4——\n");
//前三种定义字符串不是很推荐,下面是用的指针指向字符串数组,在开发中通常用这种
//如果指针操作不当,容易造成段错误sigment error
char *p = "qwkdjhqwjkd qwkldjqlkwd jhnjsda";
printf("%s\n", p); //字符串用格式占位符%s表示,不需要用i的下标遍历
return 0;
}
——1——
abcde
——2——
joker
——3——
jokersjwkjad
——4——
qwkdjhqwjkd qwkldjqlkwd jhnjsda
2.字符串的存储方式
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[3] = "123";
printf("数组的大小:%d\n", sizeof(str));
printf("数组一个元素的大小:%d\n", sizeof(str[0]));
printf("数组的长度:%d\n", sizeof(str) / sizeof(str[0]));
printf("%s\n", str);
printf("——————————————————————————\n");
char str2[] = "123";
printf("数组的大小:%d\n", sizeof(str2));
printf("数组一个元素的大小:%d\n", sizeof(str2[0]));
printf("数组的长度:%d\n", sizeof(str2) / sizeof(str2[0]));
printf("%s\n", str2);
return 0;
}
数组的大小:3
数组一个元素的大小:1
数组的长度:3
123
—————————————————————————
数组的大小:4
数组一个元素的大小:1
数组的长度:4
123
可以看到两组的存储空间大小是不一样的
str[] = {1,2,3} 系统会自动分配大小,并且在末尾加 '\0' 结束符,所以会多出一个字符
3.sizeof 、 strlen、memset、mencpy 的使用
3.1sizeof和strlen
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a[] = {1, 2, 3};
printf("数组a的元素个数是:%d\n", sizeof(a) / sizeof(a[0]));
char a2[120] = "hello";
//使用sizeof
printf("数组a2的元素个数:%d\n", sizeof(a2) / sizeof(a2[0]));
//使用strlen
printf("数组元素的个数:%d\n", strlen(a2));
return 0;
}
数组a的元素个数是:3
数组a2的元素个数:120
数组元素的个数:5
可以看到使用sizeof 得到的是定义时字符串数组的大小,这里是[120] 占120个字节
使用strlen得到的是实际字符串中内容的大小,这里是 hello 的大小,占5个字节
3.2memset 和 memcpy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str[] = "I love FishC.com!";
char *ptr; //野指针,需要开辟一个空间
int length = sizeof(str);
printf("length = %d\n", length);
ptr = (char *)malloc(length * sizeof(char)); //给野指针开辟一个空间
if (ptr == NULL) //如果申请内存不成功
{
exit(1);
}
memset(ptr, 0, length);
memcpy(ptr, str, length);
printf("%s\n", ptr);
return 0;
}
memcpy是操作内存空间,strcpy是操作字符串
函数原型
void *memcpy(void *dest, const void *src, size_t n);
注意:memcpy 函数从 src 指向的内存空间拷贝 n 个字节到 dest 指向的内存空间。src 和 dest 指向的内存区域不能出现重叠,否则应该使用 memmove 函数。
#include <stdio.h>
#include <stdio.h>
#include <string.h>
int main()
{
char a[7] = "{'\0'}";
char b[7] = "1234567";
char *c = "1234567";
memset(a, '\0', 7);
memcpy(a, b, 7); // a和b都是指针常量,操作会出错,要使用memmove
puts(a);
memset(a, '\0', 7);
memcpy(a, c, 7); //a是指针常量,c是指针变量,可以经行操作
puts(a);
memset(a, '\0', 7);
memmove(a, b, 7); //a和b都是指针常量,可以直接操作
puts(a);
return 0;
}
4.其他常用字符串操作函数
4.1puts()和 gets();
puts()输出字符串,自动换行
gets()接收字符串
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *p = "我是大帅哥";
puts(p); //自动换行
printf("%s\n", p);
//char p[128] = {'\0'}; //初始化字符串数组
char *p2; //野指针,会出错
p2 = (char *)malloc(128); //1.开辟一个空间 2.一旦用了malloc就要注意内存泄漏问题 3.会失败,要注意返回值
if (p2 == NULL)
{
printf("申请内存失败\n");
exit(-1);
}
memset(p2, '\0', 128); //初始化字符串数组
printf("输入字符串:\n");
//scanf("%s", p2);
gets(p2); //
puts(p2);
return 0;
}
我是大帅哥
我是大帅哥
输入字符串:
asdlkhjaskldjasdkj
asdlkhjaskldjasdkj
4.2strcpy 拷贝字符串数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char strDest[128] = {'\0'};
char *pstr = "我是大帅哥";
strcpy(strDest, pstr);
printf("拷贝完成后%s\n", strDest);
memset(strDest, '\0', 128); //下次调用最好重新初始化下
strcpy(strDest, "你是大帅哥");
puts(strDest);
memset(strDest, '\0', 128);
strncpy(strDest, "12345678", 5); //可以选择拷贝的长度
puts(strDest);
return 0;
}
拷贝完成后我是大帅哥
你是大帅哥
12345
4.3strcat 函数用于连接两个字符串
将源字符串拷贝并连接到目标数组存放的字符串后边,此过程将覆盖第一个参数的结束符 '\0'。
两个参数的位置不应该重叠。
#include <string.h>
...
char *strcat(char *dest, const char *src);
参数 | 含义 |
dest | 指向用于存放字符串的目标数组,它应该包含一个字符串,并且提供足够容纳连接后的总字符串长度的空间(包含结束符 '\0') |
src | 指向待连接的源字符串,该参数不应该与 dest 参数指向的位置发生重叠 |
4.4strncmp 函数用于比较两个字符串的前 n 个字符
该函数从第一个字符开始,依次比较每个字符的 ASCII 码大小,发现两个字符不相等或抵达结束符('\0')为止,或者前 n 个字符完全一样,也会停止比较。
#include <string.h>
...
int strncmp(const char *s1, const char *s2, size_t n);
返回值 | 含义 |
< 0 | 字符串 1 的字符小于字符串 2 对应位置的字符 |
0 | 两个字符串的内容完全一致 |
> 0 | 字符串 1 的字符大于字符串 2 对应位置的字符 |
4.5strchr
函数原型
char *strchr(const char *str, int c)
参数
功能
在参数str所指向的字符串中搜索第一次出现字符c(一个无符号字符)的位置。
返回值
返回一个指向该字符串中第一次出现的字符的指针,如果字符串中不包含该字符则返回NULL空指针。
示例:
#include <stdio.h>
#include <stdio.h>
#include <string.h>
int main()
{
char *str = "joker";
char c = 'k';
char *p = NULL;
p = strchr(str, c);
if (p == NULL)
{
printf("没有找到");
}
else
{
printf("找到相关的字符");
puts(p);
}
return 0;
}
找到相关的字符ker
4.6strstr
strstr()函数用于找到子串在一个字符串中第一次出现的位置,在string.h头文件中。
函数原型:char *strstr(const char *str1, const char *str2)
str1是总串,str2是需要匹配的第一个字串位置,返回值为char * 类型。
#include <stdio.h>
#include <string.h>
int main()
{
char *str1 = "123456789";
char *str2 = "456";
char *p;
p = strstr(str1, str2);
puts(p);
return 0;
}
456789
五、结构体指针
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//如果要使用结构体指针,就不能用点运算符访问结构体中的变量,要用 ->
//指针要注意是否是也只在或者NULL
struct Student
{
int score;
char name[128];
/* data */
} stu1;
int main()
{
stu1.score = 100;
strcpy(stu1.name, "张三");
printf("名字:%s,分数:%d\n", stu1.name, stu1.score);
struct Student *p; //野指针
p = (struct Student *)malloc(sizeof(struct Student));
p->score = 98;
strcpy(p->name, "李四");
printf("名字:%s,分数:%d\n", p->name, p->score);
free(p); //释放内存
p = &stu1; //指针是存放地址的变量,之前是指向malloc那块空间,现指针变量存放的是stu1的地址
printf("名字:%s,分数:%d\n", p->name, p->score);
return 0;
}
名字:张三,分数:100
名字:李四,分数:98
名字:张三,分数:100