通过前面对指针进行由浅到深的学习后今天再带领各位学习const修饰指针变量和指针笔试题。
1.const修饰指针变量
以下是一些使用const
修饰指针的示例:
代码1
int x = 10;
int y = 20;
int const * ptr = &x;
*ptr = 20; // error 解引用使得ptr值变为20,也就是x的值被修改为20
ptr = &y //使得指针ptr指向y的地址
在上述代码中,ptr
是一个指向int
的常量指针,const修饰后不能更改*ptr
的值(例如*ptr=20;)。但是,你可以通过这个指针修改它所指向的地址(例如ptr=&y)。
代码2
int x = 10;
int y = 20;
int * const ptr = &x;
*ptr = 20; //解引用使得ptr值变为20,也就是x的值被修改为20
ptr = &y // error 使得指针ptr指向y的地址
在这个例子中,ptr
是一个指向int
的常量指针,const修饰后这意味着你不能修改指针指向的地址(例如ptr=&y)。但是,你可以更改*ptr
的值(*ptr = y)。
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变
2.assert断言
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
#include<assert.h>
assert(p != NULL);//确保p不是空指针
assert ( p );//同上
assert(p1 && p2);//确保p1和p2都不是空指针
以上三种都是断言的用法,在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
aessert的好处在于,assert可以用来做参数的检查,防止出现bug,当出现问题时可以根据消息快速定位至问题处,提高debug效率。合理的使用assert可以增加代码的健壮度,同时也方便开发人员定位问题。
3.指针笔试题
在做笔试题之前需要先了解两个知识点
3.1 sizeof
sizeof是操作符,是计算所占内存的空间大小的,单位是字节不在乎内存中存放什么数据
#inculde <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a)); //4
printf("%d\n", sizeof a); //4
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(&a)); //4/8看编译器是32位还是64位
return 0;
}
3.2 strlen
1.strlen是库函数,使⽤需要包含头⽂件 string.h
2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数
3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界
4.使用函数会传递一个指针,是数组首元素的地址
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d\n", strlen(arr1));//随机值 没有\0会出现随机值
printf("%d\n", strlen(arr2));//3
printf("%d\n", sizeof(arr1));//3
printf("%d\n", sizeof(arr1));//3
return 0;
}
3.3 笔试强化
int main() {
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//字符串加上末尾的\0一共7个字节
printf("%d\n", sizeof(arr + 0));//地址的字节4/8
printf("%d\n", sizeof(*arr));//首元素的地址再解引用就是首元素1字节
printf("%d\n", sizeof(arr[1]));//指向b所占1字节
printf("%d\n", sizeof(&arr));//地址4/8
printf("%d\n", sizeof(&arr + 1));//地址4/8
printf("%d\n", sizeof(&arr[0] + 1));//地址4/8
printf("%d\n", strlen(arr));//给了首元素地址,找到\0之前的位置,6个数
printf("%d\n", strlen(arr + 0));//给了首元素地址,找到\0之前的位置,6个数
printf("%d\n", strlen(*arr));
//解引用得a,但函数需要地址就将a的ascll值97变16进制成为地址,这样就非法访问了
printf("%d\n", strlen(arr[1]));
//解引用得b,但函数需要地址就将a的ascll值98变16进制成为地址,这样就非法访问了
printf("%d\n", strlen(&arr));//这里取的是首元素地址,但步长是一个字符串字节,6个
printf("%d\n", strlen(&arr + 1));//这里是整个字符串地址,加一跳过字符串,随机值
printf("%d\n", strlen(&arr[0] + 1));//这里首元素地址加一,5个字节
return 0;
}
int main() {
int a[] = { 1,2,3,4 };
//sizeof计算操作数所占内存的⼤⼩,单位是字节
printf("%d\n", sizeof(a));//整个数组所占内存16
printf("%d\n", sizeof(a + 0));//a不是单独出现就是首元素地址,是地址就是4、8
printf("%d\n", sizeof(*a));//解引用首元素地址,所以就是首元素4
printf("%d\n", sizeof(a + 1));//a不是单独出现就是首元素地址4
printf("%d\n", sizeof(a[1]));//4
printf("%d\n", sizeof(&a));//取地址就是指针,4/8字节
printf("%d\n", sizeof(*&a));//16
printf("%d\n", sizeof(&a + 1));//取地址就是指针,4/8字节
printf("%d\n", sizeof(&a[0]));//取地址就是指针,4/8字节
printf("%d\n", sizeof(&a[0] + 1));//取地址就是指针,4/8字节
return 0;
}
int main() {
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr + 0));//4
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4
printf("%d\n", sizeof(&arr + 1));//4
printf("%d\n", sizeof(&arr[0] + 1));//4
printf("%d\n", strlen(arr));//没有结束位置是随机值
printf("%d\n", strlen(arr + 0));//首元素地址没有移动随机值
printf("%d\n", strlen(*arr));
//相当于(a),对a进行求字符串长度,因为需要输入地址,所以用a的ascll码值97转换
//为16进制当地址,最后结果是越界访问
printf("%d\n", strlen(arr[1]));//取b,同上非法访问
printf("%d\n", strlen(&arr));//随机
printf("%d\n", strlen(&arr + 1));
//整个数组的地址+1,就是跳过整个数组向后找\0,就是离\0近了一个数组的长度也就是6个字节,
//相当于离\0还有随机值-6个字节
printf("%d\n", strlen(&arr[0] + 1));
//首元素地址加一就是,第一个元素的地址,离\0还有随值-1个字节
}
int main() {
char* p = "abcdef";
printf("%d\n", sizeof(p));//这里p指的是字符串首元素地址 4/8
printf("%d\n", sizeof(p + 1));//这里是地址 4/8
printf("%d\n", sizeof(*p));//这里解引用是a,就是1字节
printf("%d\n", sizeof(p[0]));//相当于*(p+0),就是a,1字节
printf("%d\n", sizeof(&p));//4
printf("%d\n", sizeof(&p + 1));//4
printf("%d\n", sizeof(&p[0] + 1));//4
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
printf("%d\n", strlen(*p));//非法访问
printf("%d\n", strlen(p[0]));//非法访问
printf("%d\n", strlen(&p));//,&p整个数组地址,从p所占空间的起始位置开始向后查找\0
printf("%d\n", strlen(&p + 1));//随机,跳过整个数组的地址查找\0
printf("%d\n", strlen(&p[0] + 1));//b的地址,5
return 0;
}
int main() {
int a[3][4] = { 0 };
//这里为了方便理解会将二维数组拆成三个一维数组
//a[0][j], 第一个数组,a[0]就相当于数组名,&(a[0]),第一行元素地址,sizeof(a[0])是第一行所占字节大小
//a[1][j],第二个数组
//a[2][j],第三个数组
//二维数组的首元素是第一个一维数组,这个很关键
printf("%d\n", sizeof(a));//二维数组的首元素是第一行,就是48
printf("%d\n", sizeof(a[0][0]));//4
printf("%d\n", sizeof(a[0]));//单独出现就第一行数组名,放到sizeof里就是第一行所占字节16
printf("%d\n", sizeof(a[0] + 1));//不是单独出现就是第一行首元素地址,就是a[0][1]地址,就是4/8
printf("%d\n", sizeof(*(a[0] + 1)));//不是单独出现就是a[0][0]+1=a[0][1],解引用就是0,就是4
printf("%d\n", sizeof(a + 1));//二维数组首元素地址是第0行的一维数组地址,加一就是第一行数组地址
printf("%d\n", sizeof(*(a + 1)));//第一行数组地址解引用就是第一行元素,得第一行元素所占空间就是16
printf("%d\n", sizeof(&a[0] + 1));//取得是第0行地址 加一就是第一行地址,4/8
printf("%d\n", sizeof(*(&a[0] + 1)));//第一行所占空间,16
printf("%d\n", sizeof(*a));//二维数组首元素地址是第一个一维数组地址,最后就是第一行元素所占字节
printf("%d\n", sizeof(a[3]));//非法,但是类型是a行的类型,这个类型就占16
return 0;
}
#include<stdio.h>
int main()
{
int a = 5;
short s = 11;
printf("%d\n", sizeof(s = a + 2));//2
printf("%d\n", s);//11
//得出sizeof的一个结论,只看结果的类型,其他不看,表达式也不计算
return 0;
}
#include <stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);//本来&a和&a+1的地址都是int(*)[5]
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;//4
char* pcName;//4
short sDate;//2
char cha[2];//2
short sBa[4];//8
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
p = (struct Test*)0x100000;
//printf("%p\n", p + 0x1);//地址名+1跳过总地址20字节,十六进制表示就是100014
//printf("%p\n", (unsigned long)p + 0x1);//整形加一就是直接加100001
//printf("%p\n", (unsigned int*)p + 0x1);//地址加一跳过
printf("%p\n", p + 0x1);//+1-->x86环境下+20-->0x100014
printf("%p\n", (unsigned long)p + 0x1);//强制类型转换成无符号长整型
//+1-->+1-->0x100001
printf("%p\n", (unsigned int*)p + 0x1);//强制类型转换成无符号整型指针变量
//+1-->+4-->0x100004
return 0;
}
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);//1
return 0;
}
//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
//a是五个一组,p是四个一组,所以两个[4][2]是不一样的(由低到高)p-a=-4,
}
// 我们知道,指针-指针=两指针间元素个数
//可知都是低地址 - 高地址 = -4, - 4用 % d打印时 - 4; - 4用 % p打印即打印- 4的地址
//通过我们之前讲的二进制原码反码补码来计算,
//- 4的原码是10000000 00000000 00000000 00000100,
//- 4的反码是11111111 11111111 11111111 11111011,
//- 4的补码是11111111 11111111 11111111 11111100,
//即FF FF FF FC。
#include <stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);//ptr1指向10后面的元素的地址
int* ptr2 = (int*)(*(aa + 1));//二维数组的首元素是第一个一维数组的地址
//加一变成第二行元素地址解引用是6
//再强制类型转换就是ptr2指向6的地址
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
return 0;
}
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;//指向第一个字符串首字母w
pa++;//指向at
printf("%s\n", *pa);//at
return 0;
}
好了以上就是带你重新学指针的第三篇啦,如有帮助到请三连,如果有错误请多多指正