指针的理解及示例

1、内存和地址

计算机上的CPU在处理数据的时候,需要在内存中读取,处理后也会放回到内存中,内存空间是通过地址来进行管理的,每个内存单元占1个字节。

常见的换算关系如下:

1Byte=8bit,1KB=1024Byte,1MB=1024KB,1GB=1024MB,1TB=1024GB,1PB=1024TB

1bit能存下一个二进制位,4个二进制位换一个16进制位,3个二进制位换1个8进制位,一般来说在C语言中地址就是指针。在32位机器下地址占4字节,64位机器下占8字节(这是由于在32位系统下,有32根地址线,每跟地址线都能产生0或1的信号,要将地址进行编号就需要32个二进制位也就是需要32个比特位,也就是4字节,在32位机器下最多可控制2^32个内存单元。64位机器与32位机器类似,所以64位机器有64 跟地址线,占8字节,最多控制2^64个地址单元)。

2、指针变量和&操作符以及*解引用操作符

指针变量使用来存放地址的,指针变量有相应的类型,指针变量的类型决定了指针变量进行加减操作时移动的步伐,以及解引用操作所能访问内存空间的大小。

#include<stdio.h>
int main()
{
	int a = 10;//定义一个int类型的变量a
	int* p = &a;//&a表示取得a的地址,将其放入到指针变量p中,p的类型是int*
	printf("a=%d\n", a);
	printf("*p=%d\n", *p);
	*p = 20;//通过*p改变a中的值
	printf("a=%d\n", a);
	printf("*p=%d\n", *p);
	printf("&a=%p\n", &a);
	printf("p=%p\n", p);
	return 0;
}

 通过指针变量可拿到a的值,也可通过p来对a进行修改。可以看到a的地址存放在p中,指针变量p通过*操作符对其解引用,可访问a中的数据也可对a中存放的数据进行修改。p存放的是a的地址,int*是p的类型(这个*表示p是个指针变量用来存放地址,int表示p这个指针变量所指向的类型是int),如果是char ch='a';char*pc=&ch;这与上面的类似,这个*表示pc也是个指针变量,前面的char表示pc指针所指向的内容是char。同样的也可通过*pc来对ch变量进行访问或修改其中的值。

值得注意的是:指针变量也有大小,指针变量在32位机器上(有32根地址线,需要存放32位0或1,也就是需要32个bit位)所占空间位4字节,在64位平台下所占空间为8字节(有64根地址线,需要存放32位0或1,也就是需要32个bit位)。

#include<stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	printf("%zd\n", sizeof(&a));
	printf("%zd\n", sizeof(p));
	return 0;
}

指针变量的类型决定了指针变量进行加减操作时移动的步伐,以及解引用操作所能访问内存空间的大小。

测试结果如下,可以看出当不同类型的指针访问内存的空间是不同的。

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	return 0;
}

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pi = &n;
	*pi = 0;
	return 0;
}

 

 当通过指针进行解引用操作时,char指针类型只访问一个字节,int指针类型访问4个字节,所以指针类型决定了指针解引用操作访问内存空间的大小。

#include <stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
	printf("%p\n", &n);
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return 0;
}

int*类型的指针加1跳过1个int(也就是4个字节),char*类型的指针 1个char(也就是1个字节),所以指针的类型决定了指针加减操作的步伐。指针变量的类型决定了指针变量进行加减操作时移动的步伐,以及解引用操作所能访问内存空间的大小。

注意:需要通过函数改变某一类型需要传该类型的地址给函数,(如要通过函数改变int类型的变量,就要传该int类型变量的地址(如int a=10,要通过函数来改变a,传参需要传a的地址,如果要用函数改变结构体中的内容就需要用传结构体指针))

void*介绍:void*也是一个指针变量的类型,表示的是无类型的指针,能接收像char*,struct stu*,int*,float*,double*等这类型的值(这些值其实就是地址),但void*不能被解引用也不能对其进行加减操作改变地址,如果想要对其解引用或者使用这类型的指针变量需要对其进行强制类型转换。例如将其强制类型转换成int*或其他具体类型的指针。使用void*作为参数部分的例子使用可参考用冒泡排序实现类似qsort的排序函数-CSDN博客

3、const修饰指针

const放在*的左边表示不能对p所指向的内容进行修改,但是可对p进行加减地址或改变地址操作(其实就是不能对*p进行修改,但可以改变p的指向),const放在*的右边表示p不能被修改,但是*p可以改变(其实就是p所指向的内容可以修改,但不能改变p的指向)。

int a=10;
const int*p1=&a;//const放在*的左边表示不能对p1所指向的内容进行修改,但是可对p1进行加减地址或改变地址操作(其实就是不能对*p1进行修改,但可以改变p1的指向)
int const*p2=&a;//const放在*的左边表示不能对p2所指向的内容进行修改,但是可对p2进行加减地址或改变地址操作(其实就是不能对*p2进行修改,但可以改变p2的指向)
int *const p3=&a;//,const放在*的右边表示p3不能被修改,但是*p3可以改变(其实就是p3所指向的内容可以修改,但不能改变p3的指向)

 4、数组名的理解

 sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的大小,单位是字节&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的),除此之外,任何地方使用数组名,数组名都表示首元素的地址。
 

#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("&arr[0] = %p\n", &arr[0]);
    printf("&arr[0]+1 = %p\n", &arr[0]+1);
    printf("arr = %p\n", arr);
    printf("arr+1 = %p\n", arr+1);
    printf("&arr = %p\n", &arr);
    printf("&arr+1 = %p\n", &arr+1);
    return 0;
}

  在对一维数组进行传参时,参数写成数组形式,本质上还是指针,测试如下:

#include<stdio.h>
void Print(int arr[], int sz)//参数写成数组形式,本质上还是指针
{
	int sz1 = sizeof(arr)/sizeof(arr[0]);//arr是首元素的地址,在32位平台下,占4字节,而整形也是4字节,所以sz1等于1
	printf("sz1=%d\n", sz1);
	printf("sz=%d\n", sz);
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);//sizeof(arr),这里的arr表示整个数组,计算的是真个数组所占的大小,单位是字节,sizeof(arr[0])表示第一个元素所占字节的大小,两者相比就得到该数组元素的个数了
	Print(arr, sz);
	return 0;
}

 本质上数组传参传递的是数组首元素的地址。

对于二维数组而言,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个一维数组。那么⼆维数组的首元素就是第⼀行,是个⼀维数组,二维数组的首元素表示第一行的地址。二维数组传参,传的是二维数组首元素的地址(也就是第一行的一维数组的地址,所以用数组指针来接收这个地址的参数)

#include<stdio.h>
void Print(int (*arr)[5], int row,int col)//参数写成数组形式,本质上还是指针
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", *(*(arr+i)+j));//arr表示第一行那个一维数组的地址+i跳过i个一维数组,对*(arr+i)表示拿到相应的数组名其实等价于a[i],*(*(arr+i)+j)等价于a[i][j]
		}
		printf("\n");
	}

}
int main()
{
	int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
	Print(arr, 3, 5);//传入的是数组首元素的地址,也就是第一行的地址,所以需要数组指针接受
	return 0;
}

 5、一维数组二维数组示例

一维数组和二维数组在sizeof以及strlen的一些计算的示例(在x86环境下测试的):

#include<stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%zd\n", sizeof(a));//16 -- sizeof(数组名),表示整个数组的大小
	printf("%zd\n", sizeof(a + 0));//a是首元素的地址-类型是int*, a+0 还是首元素的地址,是地址大小就是4/8个字节
	printf("%zd\n", sizeof(*a));//a是首元素的地址,*a就是首元素,大小就是4个字节
	//*a == a[0] == *(a+0)
	printf("%zd\n", sizeof(a + 1));//a是首元素的地址,类型是int*,a+1跳过1个整型,a+1就是第二个元素的地址,4/8
	printf("%zd\n", sizeof(a[1]));//a[1]就是第二个元素,大小4个字节
	printf("%zd\n", sizeof(&a));//&a是数组的地址,数组的地址也是地址,是地址大小就是4/8个字节
	printf("%zd\n", sizeof(*&a));//*& 互相抵消,sizeof(*&a) = sizeof(a) -16字节
	//&a 表示数组的地址,类型是int(*)[4],对数组指针解引用访问的是数组, 计算的是数组的大小 -16字节
	printf("%zd\n", sizeof(&a + 1));//&a+1是跳过整个数组后的那个位置的地址,是地址就是4/8个字节
	printf("%zd\n", sizeof(&a[0])); //&a[0]表示首元素的地址,大小为4/8个字节
	printf("%zd\n", sizeof(&a[0] + 1));//&a[0] + 1 -- 数组第二个元素的地址,大小是4/8个字节
	return 0;
}

 

#include<stdio.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%d\n", sizeof(arr));//数组名单独放在sizeof内部了,计算的是数组的大小,单位是字节-6
	printf("%d\n", sizeof(arr + 0));//arr是数组名表示首元素的地址,arr+0还是首元素的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*arr));//arr是首元素的地址,*arr就是首元素,大小就是1个字节
	//*arr -- arr[0] - *(arr+0)
	printf("%d\n", sizeof(arr[1]));//arr[1] 是第二个元素,大小也是1个字节
	printf("%d\n", sizeof(&arr));//&arr 是数组地址,数组的地址也是地址,大小是4/8个字节
	//&arr -- char (*)[6]
	printf("%d\n", sizeof(&arr + 1));//&arr+1, 跳过整个数组,指向了数组后边的空间,4/8个字节
	printf("%d\n", sizeof(&arr[0] + 1));//第二个元素的地址,是地址就是4/8字节

	return 0;
}

 

#include<stdio.h>
#include <string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//arr是首元素的地址,数组中没有\0,就会导致越界访问,结果就是随机的k
	printf("%d\n", strlen(arr + 0));//arr+0是数组首元素的地址,数组中没有\0,就会导致越界访问,结果就是随机的k
	//printf("%d\n", strlen(*arr));//arr是首元素的地址,*arr是首元素,就是'a','a'的ascii码值是97
	//就相当于把97作为地址传递给了strlen,会造成非法访问
	//printf("%d\n", strlen(arr[1]));//arr[1]就是'b',其ascii码值是98,传给strlen函数,造成非法访问,这句代码错误
	printf("%d\n", strlen(&arr));//&arr是数组的地址(会报警告,指针类型不匹配)
	//起始位置是数组的第一个元素的位置,随机值k
	printf("%d\n", strlen(&arr + 1));//跳过该数组,从数组的后面开始计算,直到遇到'\0',所以是随机值k-6
	printf("%d\n", strlen(&arr[0] + 1));//从arr数组中的第2个元素开始向后统计的,得到的也是随机值 k-1
	return 0;
}

 

#include<stdio.h>
#include <string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));//arr是数组名,单独放在sizeof内部,计算的是数组总大小,是7个字节
	printf("%d\n", sizeof(arr + 0));//arr表示数组首元素的地址,arr+0还是首元素的地址,是地址就是4/8
	printf("%d\n", sizeof(*arr));//arr表示数组首元素的地址,*arr就是首元素,大小是1字节
	printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1个字节
	printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8
	printf("%d\n", sizeof(&arr + 1));//&arr是数组的地址,+1跳过整个数组,还是地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,大小是4/8个字节
	return 0;
}

 

#include <string.h>
#include<stdio.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//arr首元素的地址,arr+0还是首元素的地址,向后在\0之前有6个字符
	//printf("%d\n", strlen(*arr));//arr是首元素的地址,*arr是首元素,就是'a','a'的ascii码值是97,会造成非法访问
	//printf("%d\n", strlen(arr[1]));//arr[1]是'b','b'的ascii码值是98,回造成非法访问
	printf("%d\n", strlen(&arr));//&arr是数组的地址,也是从数组第一个元素开始向后找,6
	//&arr的类型是char (*)[7]
	//size_t strlen(const char* s);
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
	return 0;
}

#include<stdio.h>
int main()
{
	const char * p = "abcdef";

	printf("%d\n", sizeof(p));//p是指针变量,指向的是a的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(p + 1));//p + 1是b的地址,是地址大小就是4/8个字节
	printf("%d\n", sizeof(*p));//p的类型是const char*, *p就是char类型了,1个字节
	printf("%d\n", sizeof(p[0]));//p[0]等价于*(p+0)等价于*p,也就是'a',大小是1字节
	printf("%d\n", sizeof(&p));//取出的是p的地址,地址的大小是4/8个字节
	printf("%d\n", sizeof(&p + 1));//&p + 1是跳过p指针变量后的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&p[0] + 1));//4/8字节 &p[0]-取出字符串首字符的地址,+1是第二个字符的地址,大小是4/8个字节
	return 0;
}

#include<stdio.h>
int main()
{
	const char* p = "abcdef";
	printf("%d\n", sizeof(p));//p是指针变量,指向的是a的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(p + 1));//p + 1是b的地址,是地址大小就是4/8个字节
	printf("%d\n", sizeof(*p));//p的类型是const char*, *p就是char类型了,1个字节
	printf("%d\n", sizeof(p[0]));//p[0]等价于*(p+0)等价于*p,也就是'a',大小是1字节
	printf("%d\n", sizeof(&p));//取出的是p的地址,地址的大小是4/8个字节
	printf("%d\n", sizeof(&p + 1));//&p + 1是跳过p指针变量后的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&p[0] + 1));//4/8字节 &p[0]-取出字符串首字符的地址,+1是第二个字符的地址,大小是4/8个字节
	return 0;
}

#include<stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//a是二维数组名,单独放到sizeof内部,得到是整个二维数组的大小,48个字节
	printf("%d\n", sizeof(a[0][0]));//a[0][0]表示第一行第一个元素,是整形就是4个字节
	printf("%d\n", sizeof(a[0]));//a[0]表示二维数组首元素的地址,也就是第一行一维数组的数组名,
	//数组名单独放到sizeof内部计算的是整个数组的大小(也就是计算的是第一行这个一维数组的大小),所以是16字节
	printf("%d\n", sizeof(a[0] + 1));//这里的a[0]表示第一行的数组名,没有单独放到sizeof内部
	//表示第一行首元素的地址,+1跳过一个整形,a[0]+1是第一行第二列的地址,是地址就是4/8个字节

	printf("%d\n", sizeof(*(a[0] + 1)));//*(a[0] + 1)表示第一行第二个元素,是整形所以是4字节
	printf("%d\n", sizeof(a + 1));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址
	//二维数组首元素的地址就是第一行的地址,也就是第一行这个一维数组的地址
	//a+1,跳过一行,指向了第二行,a+1是第二行的地址,a+1是数组指针,是地址大小就是4/8个字节
	//注意:不论是何种类型的指针,都占4/8个字节
	printf("%d\n", sizeof(*(a + 1)));//a+1是第二行的地址,*(a+1)就是第二行,计算的是第二行的大小 - 16
	//*(a + 1) == a[1], a[1]是第二行的数组名,sizeof(*(a + 1))就相当于sizeof(a[1]),意思是把第二行的数组名单独放在
	//sizeof内部,计算的是第二行的大小
	printf("%d\n", sizeof(&a[0] + 1));//a[0]是第一行的数组名,&a[0]取出的就是数组的地址,就是第一行的地址
	//&a[0]+1 就是第二行数组的地址,是地址大小就是4/8个字节
	printf("%d\n", sizeof(*(&a[0] + 1)));//*(&a[0] + 1)意思是对第二行的地址解引用,访问的就是第二行,大小是16字节
	printf("%d\n", sizeof(*a));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址是
	//也就第一行的地址,*a就是第一行,计算的就是第一行的大小,16字节
	//*a == *(a+0) == a[0]
	printf("%d\n", sizeof(a[3]));//a[3]无需真实存在,仅仅通过类型的推断就能算出长度
	//a[3]是第四行的数组名,单独放在sizeof内部,计算的是第四行的大小,16个字节
	return 0;
}

 感谢观看,如有错误不足之处欢迎大家批评指正。

  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值