
--变量是可变的,指针变量也是如此;想要限制变量的修改,就要加上限制,就用到了const修饰;
int main()
{
const int n = 10;
n = 20;
printf("%d\n", n);
return 0;
}
--可见,const修饰后,变量n无法修改。
--本质上,n还是变量,只是语法层面上限制了n的修改,n变成常变量;
--如果我们绕过n,使用n的地址,就能修改n,但是在打破语法规则。
int main()
{
const int n = 10;
int* p = &n;
*p = 20;
printf("%d\n", n);
return 0;
}--const修饰指针变量可以放在"*"的左边或右边,但起的作用不尽相同;
int* p//无const修饰
const int* p//在左
int* const p//在右--下面,看代码进行理解:
/代码块1 —— 测试⽆const修饰的情况
void test1()
{
int n = 10;
int m = 20;
int* p = &n;
*p = 20;//ok
p = &m; //ok
}
//代码块2 —— 测试const放在*的左边情况
void test2()
{
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//no
p = &m; //ok
}
//代码块3 —— 测试const放在*的右边情况
void test3()
{
int n = 10;
int m = 20;
int* const p = &n;
*p = 20; //ok
p = &m; //no
}
//代码块4 —— 测试*的左右两边都有const
void test4()
{
int n = 10;
int m = 20;
int const* const p = &n;
*p = 20; //no
p = &m; //no
}——结论:
--野指针:指指针指向的位置是不可知的(随机的、不确定的、没有明确限定的),即指向的空间不属于当前程序。
int mian()
{
int* p;//局部变量指针未初始化,默认随机值
*p = 20;
return 0;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i <= sz; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}
--看结果,指针访问超过了数组本身长度,在arr[10]时,指针返回随机值,即野指针。
--意味着该指针所指向的内存空间已被销毁(或归还给系统),指向对象不存在(生命周期结束)。
int* test()
{
int n = 100;
return &n;
//函数返回的是地址
}
int main()
{
int* p = test();
printf("%d\n", *p);//无法打印n,函数返回后,局部变量n的生命周期结束
return 0;
}--函数调用时,局部变量 n 被分配在栈上;函数返回时,栈帧)被弹出, n的内存被系统标记为可复用。
--有时会“侥幸”输出100,但这是完全不可靠的。后续函数调用会覆盖该内存。
--当使用指针时,知道指针指向的的对象就直接赋值相应的地址;不知道,可以初始化为空指针——NULL。
-- NULL 是C语言定义的标识符常量,表示指针不指向任何有效的内存地址,值为0;0表示地址,但是无法使用,会报错。
int main()
{
int a = 10;
int* p = NULL;
return 0;
}--一个程序向内存申请了多大的空间,通过指针进行访问也就多大,不能超出范围;比如上面数组的访问就是越界。
--当指针变量指向一块区域时,可以通过指针访问该区域,当后期不再使用这个指针访问时,就可以把指针置NULL;规则:只要是NULL指针就不去访问;同时注意使用指针时判断是否为NULL。
--比如2.1.3,变量已销毁,再返回地址无法找到对象。
--asser.h头文件定义了宏asser(),用于运行时确保程序符合指定条件,不符合就报错终止;这个宏被称为“断言”。
#include <assert.h>
assert(p != NULL); - --当条件为真时:程序继续正常执行;
- --当条件为假时:输出错误信息(包含文件名、行号、失败条件);
--使用assert()的好处:
#define NDEBUG
#include <assert.h>--这是程序重新编译,禁用文件中所有的assert()语句;当程序有有类似问题时,可以将宏注释掉。
--当然存在缺点:会降低程序运行效率。
--VS平台,一般在Debug版本使用,利于排查问题;在Release版本会直接优化掉,不影响使用效率。
--函数strlen的作用是求字符串长度,计算 ' \0 ' 之前的字符个数。
size_t my_strlen(const char* str)//const防止通过指针修改指向的内容
{
size_t count = 0;
//保护指针
asser(str != NULL);
while (*str != '\0')
{
count++;
str++;
return count;
}
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%d\n", len);
return 0;
}--那我们对指针的了解不了少,那什么问题才需要指针来解决呢?
--举例——写一个函数,交换两个整形变量的值:
--首先不用指针来写:
//定义函数
void Swep1(int x, int y)
{
int z = 0;
z = x;
x = y;
y = z;
}
int main()
{
int a = 10;
int b = 20;
printf("交换之前,a = %d,b = %d\n", a, b);
Swep1(a, b);
printf("交换之后,a = %d,b = %d\n", a, b);
}
--发现并没有实现想要的功能!为什么呢?
--当调试后,发现形参和实参的地址不同,所以不管函数内参数如何变化都不会影响实参。
--因为形参是实参的临时拷贝(数值),但二者都是独立变量,这就叫做传值调用。
--既然如此,将地址一并传过去呢?就用到了指针:
void Swep2(int* pa, int* pb)
{
//*pa//a
//*pb//b
int z = *pa;//z = a
*pa = *pb;//a = b
*pb = z;//b = a
}
int main()
{
int a = 10;
int b = 20;
printf("交换之前,a = %d,b = %d\n", a, b);
Swep2(&a, &b);
printf("交换之后,a = %d,b = %d\n", a, b);
}
--将变量的地址传给函数,叫做传址调用;
--传址调用,让函数和主调函数之间建立了真正的联系,在函数内部就可以修改主函数的变量;
--在设计函数时,如果只需要主函数变量的值计算就用传值;如果函数涉及到修改主函数变量的值,用传址。
结语:本篇内容就到这里了,主要分享了指针的一些基础内容,后续仍会分享指针的相关知识;指针的内容需要反复研读 ,如果这篇文章对你的学习有帮助的话,欢迎一起讨论学习,你这么帅给个三连吧~~~