带你重新学习指针3

通过前面对指针进行由浅到深的学习后今天再带领各位学习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;
}

好了以上就是带你重新学指针的第三篇啦,如有帮助到请三连,如果有错误请多多指正

  • 33
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值