4.const修饰指针
4.1const关键字
1):const修饰普通变量
int main()
{
const int num = 0;
num = 20;
printf("%d",num);
return 0;
}
如上图所示代码,当我们用const修饰num后,试图更改num的值并运行时,会出现以下报错:
由此可见,被const修饰的普通变量,其内容无法被直接修改。
注:C语言中,这里的num为常变量(本质仍为变量),因为有const修饰,编译器在语法上不允许直接修改这一变量;而C++中,这里的num为常量。
但是,当我们通过指针,间接地修改num的值时,会发现程序一切正常。如下图所示:
#include <stdio.h>
int main()
{
const int num = 0;
int* pn = #
*pn = 20;
printf("%d", num);
return 0;
}
因此,对于const修饰的普通变量来说,它自己不能被直接的修改,但是我们可以用指针去间接的修改它。
2):const修饰指针变量
通常来讲,const修饰指针变量有两种情况(假设 pi 是 i 的指针):
(1)const放在 * 左边:int const * pi:这种情况下,可以理解为const修饰的是 *pi(对 pi 解引用后就是 i ) ,因此指针指向的内容(即 i 的内容)就不能修改。
(2)const放在 * 右边:int * const pi:这种情况下,可以理解为const修饰的是 pi (指针变量本身的内容),因此指针指向的内容(即 i 的地址)就不能修改。
注:特殊的,若两个地方均有const修饰,那么 pi 和 *pi 均不能被修改(即 i 的地址和内容)。
5.指针运算
5.1指针+/-整数
指针+/-整数的实际含义,我们已经在《C语言的指针(一)》中学习过了,这里我们就以一个实际的例子来加深我们的理解,我们将通过指针完成对数组内元素的打印:
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* parr = &arr[0];//创建一个指针变量,指向数组的首元素
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
for (int i = 0; i <= sz - 1; i++)
{
printf("%d ", *(parr + i));
}
return 0;
}
附运行截图:
5.2指针-指针(地址-地址)
1):先出结论:指针-指针(的绝对值),得到的是指针和指针间的元素个数。我们可以如下图代码尝试一下:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%2d\n", &arr[9] - &arr[0]);
printf("%2d\n", &arr[0] - &arr[9]);
return 0;
}
附运行截图:
注:这种运算的前提条件是:两个指针必须指向同一块空间(比如同一个数组内)
5.3指针的关系运算
1):自己尝试实现strlen函数。
在这之前,我们需要学习两个知识点:
(1)strlen函数用于求字符串的字符个数,即 ‘ \0 '之前的字符个数(每个字符串末尾都有 ' \0 ' 用来表示字符串的结束)。
(2)数组名实际上就是数组首元素的地址。
接下来,我们着手实现这一函数:
#include <stdio.h>
size_t strlen_self(char* arr)
{
char* s =arr ;
size_t count = 0;
while (*s != '\0')
{
s++;//跳到下一个字符
count++;//计数
}
return count;
}
int main()
{
char arr[] = "abcdef";
printf("%zd", strlen_self(arr));
}
6.野指针
6.1概念
野指针即指向的位置是不可知的(随机的、不正确的、没有明确限制的)的指针。
6.2成因
1):指针未初始化:
include <stdio.h>
int main()
{
int* p ;//p没有初始化p里面的地址为随机值
*p = 20;//p指向的空间不属于这一程序,形成了非法访问
printf("%d", *p);
return 0;
}
2): 指针越界访问:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int i = 0;
for (i = 0; i <= 11; i++)
{
*p = i;
p++;//i==11时,指针超出了数组范围,越界访问形成了野指针
}
return 0;
}
3):指针指向的空间释放了
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int* p = test();//p指向的空间被释放了,那块空间可能已经不属于该程序了
printf("%d\n", *p);
return 0;
}
6.3如何规避野指针
1):指针初始化:如果明确知道指针指向哪里,就直接赋地址,若不能,则可以给指针赋值NULL。NULL是C语言中定义的一个标识符常量,值是0,0也是地址,但这个地址是无法使用的,读写该地址会报错。
int n = 0;
int* p1 = &n;
int* p2 = NULL;//NULL空指针
*关于0:0—数字—0、’ 0 ‘—字符0 —ASCII值为48、’ \0 ’—转义字符—0、NULL—空指针—0
2):小心指针越界:一个程序申请了哪些内存空间,指针就只能访问这些内存空间,不能超出范围访问,超出了就是越界访问。
3):指针变量不再使用时,及时置NULL,指针使用前检查其有效性。
当指针变量指向一块区域时,我们可以通过指针访问该区域,后期不再使用这个指针访问空间时,我们可以将该指针置为NULL。因为我们写代码时约定俗成的一个规则就是:只要是NULL指针就不去访问,同时使用指针之前判断指针是否为NULL。
4):避免返回局部变量的地址
7.assert断言
assert.h头文件定义了 “ 宏assert(表达式)” ,用于在运行时确保程序符合指定条件。如果不符合,就报错终止运行。这个宏常常被称为“断言”。例如:
assert( p != NULL )
assert能自动标识出出错文件及行号,且无需改代码就能开启/关闭。关闭assert( )的机制如下:
#define NDEBUG
但是其缺点为:引入了额外检查,增加了运行时间。
——指针(二)完