当前位置:网站首页>第二部分—C语言提高篇_3. 指针强化
第二部分—C语言提高篇_3. 指针强化
2022-07-24 06:13:00 【qq_43205256】
3.1 指针是一种数据类型
3.1.1 指针变量
指针是一种数据类型,占用内存空间,用来保存内存地址。
void test01()
{
int* p1 = 0x1234;
int*** p2 = 0x1111;
printf("p1 size:%d\n",sizeof(p1));
printf("p2 size:%d\n",sizeof(p2));
//指针是变量,指针本身也占内存空间,指针也可以被赋值
int a = 10;
p1 = &a;
printf("p1 address:%p\n", &p1);
printf("p1 address:%p\n", p1);
printf("a address:%p\n", &a);
}输出结果
p1 size:4
p2 size:4
p1 address:003DFC50
p1 address:003DFC38
a address:003DFC383.1.2 野指针和空指针
3.1.2.1 空指针
标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可以给它赋值一个零值。
如果对一个NULL指针间接访问会发生什么呢?结果因编译器而异。
不允许向NULL和非法地址拷贝内存:
void test()
{
char *p = NULL;
//给p指向的内存区域拷贝内容
strcpy(p, "1111"); //err
char *q = 0x1122;
//给q指向的内存区域拷贝内容
strcpy(q, "2222"); //err
}4.1.2.2 野指针
在使用指针时,要避免野指针的出现:
野指针指向一个已删除的对象或未申请访问受限内存区域的指针。
什么情况下会导致野指针?
1.指针变量未初始化 任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。 2.指针释放后未置空 有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。 3.指针操作超越变量作用域 不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。 |
操作野指针是非常危险的操作,应该规避野指针的出现:
1.初始化时置 NULL 指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。 2.释放时置 NULL 当指针p指向的内存空间释放时,没有设置指针p的值为NULL。delete和free只是把内存空间释放了,但是并没有将指针p的值赋为NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。 |
3.1.3 间接访问操作符
通过一个指针访问它所指向的地址的过程叫做间接访问,或者叫解引用指针,这个用于执行间接访问的操作符是*。
注意:对一个int*类型指针解引用会产生一个整型值,类似地,对一个float*指针解引用会产生了一个float类型的值。
//解引用
void test01()
{
int* p = NULL; //定义指针
int a = 10;
p = &a; //指针指向谁,就把谁的地址赋给指针
*p = 20;
int b = *p;
char* str = "hello world!"; //必须确保内存可写
//*str = 'm'; //错误,字符串常量不可改变
printf("a:%d\n", a);
printf("*p:%d\n", *p);
printf("b:%d\n", b);
}3.1.4 指针的步长
指针是一种数据类型,是指它指向的内存空间的数据类型。指针所指向的内存空间决定了指针的步长。指针的步长指的是,当指针+1时候,移动多少字节单位。
思考如下问题:
int a = 0xaabbccdd;
unsigned int *p1 = &a;
unsigned char *p2 = &a;
//为什么*p1打印出来正确结果?
printf("%x\n", *p1);
//为什么*p2没有打印出来正确结果?
printf("%x\n", *p2);
//为什么p1指针+1加了4字节?
printf("p1 =%d\n", p1);
printf("p1+1=%d\n", p1 + 1);
//为什么p2指针+1加了1字节?
printf("p2 =%d\n", p2);
printf("p2+1=%d\n", p2 + 1);输出结果
aabbccdd
dd
p1 =6028576
p1+1=6028580
p2 =6028576
p2+1=60285773.2 指针的意义_间接赋值
3.2.1 间接赋值的三大条件
通过指针间接赋值成立的三大条件:
1. 2个变量(一个普通变量一个指针变量、或者一个实参一个形参)
2. 建立关系
3. 通过 * 操作指针指向的内存
void test()
{
int a = 100; //两个变量
int *p = NULL;
p = &a; //建立关系
*p = 22; //通过*操作内存
}3.2.2 多级指针
void test()
{
int b;
int *q = &b;
int **t = &q;
int ***m = &t;
}3.2.3 间接赋值:从0级指针到1级指针
int func1()
{
return 10;
}
void func2(int a)
{
a = 100;
}
void test02()
{
int a = 0;
a = func1();
printf("a = %d\n", a);
func2(a);
printf("a = %d\n", a); //为什么没有修改?
}
//指针的间接赋值
void func3(int* a)
{
*a = 100;
}
void test03()
{
int a = 0;
func3(&a);
printf("a = %d\n", a); //修改成功
}3.2.4 间接赋值:从1级指针到2级指针
void AllocateSpace(char** p)
{
*p = (char*)malloc(100);
strcpy(*p, "hello world!");
}
void FreeSpace(char** p)
{
if (p == NULL)
{
return;
}
if (*p != NULL)
{
free(*p);
*p = NULL;
}
}
void test()
{
char* p = NULL;
AllocateSpace(&p);
printf("%s\n",p);
FreeSpace(&p);
if (p == NULL)
{
printf("p内存释放!\n");
}
}输出结果
hello world!
p内存释放!3.3 指针做函数参数
指针做函数参数,具备输入和输出特性:
- 输入:主调函数分配内存
- 输出:被调用函数分配内存
3.3.1 输入特性
void fun(char *p)
{
strcpy(p, "abcddsgsd"); //给p指向的内存区域拷贝内容
}
void test()
{
char buf[100] = { 0 }; //输入,主调函数分配内存
fun(buf);
printf("buf = %s\n", buf);
}输出结果
buf = abcddsgsd3.3.2 输出特性
void fun(char **p , int *len)
{
char *tmp = (char *)malloc(100);
if (tmp == NULL)
{
return;
}
strcpy(tmp, "adlsgjldsk");
//间接赋值
*p = tmp;
*len = strlen(tmp);
}
void test()
{
char *p = NULL; //输出,被调用函数分配内存,地址传递
int len = 0;
fun(&p, &len);
if (p != NULL)
{
printf("p = %s, len = %d\n", p, len);
}
}输出结果
p = adlsgjldsk, len = 103.4 字符串指针强化
3.4.1 字符串指针做函数参数
3.4.1.1 字符串基本操作
//字符串基本操作
//字符串是以0或者'\0'结尾的字符数组,(数字0和字符'\0'等价)
void test01()
{
//字符数组只能初始化5个字符,当输出的时候,从开始位置直到找到0结束
char str1[] = { 'h', 'e', 'l', 'l', 'o' };
printf("%s\n",str1);
//字符数组部分初始化,剩余填0
char str2[100] = { 'h', 'e', 'l', 'l', 'o' };
printf("%s\n", str2);
//如果以字符串初始化,那么编译器默认会在字符串尾部添加'\0'
char str3[] = "hello";
printf("%s\n",str3);
printf("sizeof str:%d\n",sizeof(str3));
printf("strlen str:%d\n",strlen(str3));
//sizeof计算数组大小,数组包含'\0'字符
//strlen计算字符串的长度,到'\0'结束
//那么如果我这么写,结果是多少呢?
char str4[100] = "hello";
printf("sizeof str:%d\n", sizeof(str4));
printf("strlen str:%d\n", strlen(str4));
//请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
char str5[] = "hello\0world";
printf("%s\n",str5);
printf("sizeof str5:%d\n",sizeof(str5));
printf("strlen str5:%d\n",strlen(str5));
//再请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
char str6[] = "hello\012world";
printf("%s\n", str6);
printf("sizeof str6:%d\n", sizeof(str6));
printf("strlen str6:%d\n", strlen(str6));
}输出结果
hello烫烫烫蘕懆鹹
hello
hello
sizeof str:6
strlen str:5
sizeof str:100
strlen str:5
hello
sizeof str5:12
strlen str5:5
hello ‘/012’为换行符
world
sizeof str6:12
strlen str6:11八进制和十六进制转义字符:
在C中有两种特殊的字符,八进制转义字符和十六进制转义字符,八进制字符的一般形式是'\ddd',d是0-7的数字。十六进制字符的一般形式是'\xhh',h是0-9或A-F内的一个。八进制字符和十六进制字符表示的是字符的ASCII码对应的数值。 比如 :
|
3.4.1.2 字符串拷贝功能实现
//拷贝方法1
void copy_string01(char* dest, char* source )
{
for (int i = 0; source[i] != '\0';i++)
{
dest[i] = source[i];
}
}
//拷贝方法2
void copy_string02(char* dest, char* source)
{
while (*source != '\0')
{
*dest = *source;
source++;
dest++;
}
}
//拷贝方法3
void copy_string03(char* dest, char* source)
{
//判断*dest是否为0,0则退出循环
while (*dest++ = *source++){}
}3.4.1.3 字符串反转模型

void reverse_string(char* str)
{
if (str == NULL)
{
return;
}
int begin = 0;
int end = strlen(str) - 1;
while (begin < end)
{
//交换两个字符元素
char temp = str[begin];
str[begin] = str[end];
str[end] = temp;
begin++;
end--;
}
}
void test()
{
char str[] = "abcdefghijklmn";
printf("str:%s\n", str);
reverse_string(str);
printf("str:%s\n", str);
}输出结果
str:abcdefghijklmn
str:nmlkjihgfedcba3.4.2字符串的格式化
3.4.2.1 sprintf
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直 到出现字符串结束符 '\0' 为止。
参数:
str:字符串首地址
format:字符串格式,用法和printf()一样
返回值:
成功:实际格式化的字符个数
失败: - 1
void test()
{
//1. 格式化字符串
char buf[1024] = { 0 };
sprintf(buf, "你好,%s,欢迎加入我们!", "John");
printf("buf:%s\n",buf);
memset(buf, 0, 1024);
sprintf(buf, "我今年%d岁了!", 20);
printf("buf:%s\n", buf);
//2. 拼接字符串
memset(buf, 0, 1024);
char str1[] = "hello";
char str2[] = "world";
int len = sprintf(buf,"%s %s",str1,str2);
printf("buf:%s len:%d\n", buf,len);
//3. 数字转字符串
memset(buf, 0, 1024);
int num = 100;
sprintf(buf, "%d", num);
printf("buf:%s\n", buf);
//设置宽度 右对齐
memset(buf, 0, 1024);
sprintf(buf, "%8d", num);
printf("buf:%s\n", buf);
//设置宽度 左对齐
memset(buf, 0, 1024);
sprintf(buf, "%-8d", num);
printf("buf:%s\n", buf);
}输出结果
buf:你好,John,欢迎加入我们!
buf:我今年20岁了!
buf:hello world len:11
buf:100
buf: 100
buf:1003.4.2.2 sscanf
#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:
str:指定的字符串首地址
format:字符串格式,用法和scanf()一样
返回值:
成功:成功则返回参数数目,失败则返回-1
失败: - 1
格式 | 作用 |
%*s或%*d | 跳过数据 |
%[width]s | 读指定宽度的数据 |
%[a-z] | 匹配a到z中任意字符(尽可能多的匹配) |
%[aBc] | 匹配a、B、c中一员,贪婪性 |
%[^a] | 匹配非a的任意字符,贪婪性 |
%[^a-z] | 表示读取除a-z以外的所有字符 |
//1. 跳过数据
void test01()
{
char buf[1024] = { 0 };
//跳过前面的数字
//匹配第一个字符是否是数字,如果是,则跳过
//如果不是则停止匹配
sscanf("123456aaaa", "%*d%s", buf);
printf("buf:%s\n",buf);
}
//2. 读取指定宽度数据
void test02()
{
char buf[1024] = { 0 };
//跳过前面的数字
sscanf("123456aaaa", "%7s", buf);
printf("buf:%s\n", buf);
}
//3. 匹配a-z中任意字符
void test03()
{
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符,判断字符是否是a-z中的字符,如果是匹配
//如果不是停止匹配
sscanf("abcdefg123456", "%[a-z]", buf);
printf("buf:%s\n", buf);
}
//4. 匹配aBc中的任何一个
void test04()
{
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
sscanf("abcdefg123456", "%[aBc]", buf);
printf("buf:%s\n", buf);
}
//5. 匹配非a的任意字符
void test05()
{
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
sscanf("bcdefag123456", "%[^a]", buf);
printf("buf:%s\n", buf);
}
//6. 匹配非a-z中的任意字符
void test06()
{
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
sscanf("123456ABCDbcdefag", "%[^a-z]", buf);
printf("buf:%s\n", buf);
}输出结果
buf:aaaa
buf:123456a
buf:abcdefg
buf:a
buf:bcdef
buf:123456ABCD3.5 一级指针易错点
3.5.1 越界
void test()
{
char buf[3] = "abc"; //用完三个字节空间,无法放下‘\0’
printf("buf:%s\n",buf);
}输出结果
buf:abc烫烫蘟BRZ3.5.2 指针自增会影响释放空间
void test()
{
char *p = (char *)malloc(50);
char buf[] = "abcdef";
int n = strlen(buf);
int i = 0;
for (i = 0; i < n; i++)
{
*p = buf[i];
p++; //修改原指针指向
}
free(p); //报错
}3.5.3 返回局部变量地址
char *get_str()
{
char str[] = "abcdedsgads"; //栈区,
return str;
}
int main()
{
printf("[get_str]str = %s\n", get_str()); //栈区空间已释放,无法输出字符串
return 0;
}输出结果
[get_str]str = 烫烫烫烫烫烫0???3.5.4 同一块内存释放多次(不可以释放野指针)
void test()
{
char *p = NULL;
p = (char *)malloc(50);
strcpy(p, "abcdef");
if (p != NULL)
{
//free()函数的功能只是告诉系统 p 指向的内存可以回收了
// 就是说,p 指向的内存使用权交还给系统
//但是,p的值还是原来的值(野指针),p还是指向原来的内存
free(p);
}
if (p != NULL) //报错,因为二次释放
{
free(p);
}
}3.6 const使用
//1.const修饰变量
void test01()
{
const int i = 0;
//i = 100; //错误,只读变量初始化之后不能修改
const int j; //定义const变量最好初始化
//j = 100; //错误,不能再次赋值
const int k = 10; //const是一个只读变量,并不是一个常量,可通过指针间接修改
//k = 100; //错误,不可直接修改,但可通过指针间接修改
int* p = &k;
*p = 100;
printf("k:%d\n", k);
}
//2.const 修饰指针
void test02()
{
int a = 10;
int b = 20;
const int* p_a = &a;
//*p_a = 100; //不可修改指针指向的值
p_a = &b; //可修改指针的指向
//const放在*号的右侧, 修饰指针的指向不能修改,但是可修改指针指向的内存空间
int* const p_b = &a;
//p_b = &b; //不可修改指针的指向
*p_b = 100; //可修改指针指向的值
//指针的指向和指针指向的值都不能修改
const int* const p_c = &a;
}
//3.const修饰结构体
struct Person
{
char name[64];
int id;
int age;
int score;
};
//每次都对对象进行拷贝,效率低,应该用指针
/*void printPersonByValue(struct Person person)
{
printf("Name:%s\n", person.name);
printf("Name:%d\n", person.id);
printf("Name:%d\n", person.age);
printf("Name:%d\n", person.score);
}*/
//但是用指针会有副作用,可能会不小心修改原数据,所以加上const
void printPersonByPointer(const struct Person *person)
{
printf("Name:%s\n", person->name);
printf("Name:%d\n", person->id);
printf("Name:%d\n", person->age);
printf("Name:%d\n", person->score);
}
void test03()
{
struct Person p = { "Obama", 1101, 23, 87 };
//printPersonByValue(p);
printPersonByPointer(&p);
}边栏推荐
- [USB voltmeter and ammeter] Based on stm32f103c8t6 for Arduino
- Prediction of advertising investment and sales based on regression analysis -- K neighborhood, decision tree, random forest, linear regression, ridge regression
- 极客星球丨 字节跳动一站式数据治理解决方案及平台架构
- 永远不要迷失自我!
- Vs2019 configuration running open3d example
- You don't have to waste your life on others' standards
- [waveform / signal generator] Based on stc1524k32s4 for C on Keil
- Cmake notes
- 济南人社已签1W+电子劳动合同,法大大助力HR数字化
- XXL execute node error log swiping
猜你喜欢

济南人社已签1W+电子劳动合同,法大大助力HR数字化

xavier_ normal_ Initialization test
![[USB voltmeter and ammeter] Based on stm32f103c8t6 for Arduino](/img/5f/40e4e144a628ef1aa38ab536b40366.png)
[USB voltmeter and ammeter] Based on stm32f103c8t6 for Arduino

Redis 分片集群

【方向盘】IDEA的代码审查能力,来保证代码质量

C语言中extern,static, register,volatile 关键字的作用;保姆级教学!

(笔记整理未完成)【图论:求单源最短路径】

STM32基于hal库的adc以DMA的多通道采样以及所遇问题解决

Three level classification / menu query tree structure

Penetration learning - SQL injection - shooting range - installation and bypass experiment of safety dog (it will be updated later)
随机推荐
Processing tree structured data
MGR_ mysqlsh_ Keepalive high availability architecture deployment document
Input the names of 10 people and output them in descending order
Vs2019 configuration running open3d example
Getting started with redis
【学习笔记】url输入到页面展现中发生了什么?
Sealos packages and deploys kubesphere container platform
MySQL automatic generation creation time and update time
Cmake notes
reflex
xavier_normal_ 初始化测试
(static, dynamic, file) three versions of address book
Huawei experts' self statement: how to become an excellent engineer
lambda表达式对list对象进行多字段排序
你不可能让每个人都满意!
chm文件打开时提示乱码
【学习笔记】Web页面渲染的流程
SPI——发送16位和8位数据
Redis 分片集群
Love yourself first, then others.