C语言——指针详细讲解

目录

一、指针

二、字符指针

三、指针数组

四、数组指针

五、数组参数、指针参数

六、函数指针

七、函数指针数组

八 、指向函数指针数组的指针

九、回调函数

十、深度解析指针和数组


一、指针

(一)、指针的定义

    数据是存放在内存中的,每一个内存空间都会有一个像房间号一样的编号,比如某某五星级酒店3109号房间,只有获得了这个编号才能找到这个房间,这个编号就是地址。而指针就是用来存放这个地址的变量。
    总结来说:指针是一个变量,是用来存放内存地址的变量,我们通常称谓指针变量。
    ①指针图示讲解:

    ②指针的大小:是由计算机的物理性质决定的,一般的计算机分为32位机和64位机,32位机就是由32根地址线组成,每一根地址线都会传递高电平(1)或者低电平(0)。那么32根地址线就会产生2^32次方个地址,所以64位机的地址以此类推。8bite = 1字节——32位机的地址就由4个字节存储,62位机的地址就由8个字节的地址存储。
(二)、指针和指针类型
    数据的存储方式有
char、short、int、long、long long、float、double、struct、void
    因此对应反指针类型也就有
char*、short*、int*、long*、long long*、float*、double*、struct*、void*
    指针的使用
int main()
{

    //什么类型数据的地址就得放到对应类型的指针变量中
    
    char ch = 'w';
    char* pc = &ch;

    int num = 10;
    int* p = #

    return 0;
}

(三)、指针的解引用

    指针的类型决定了,对指针解引用的时候有多大的权限(能取到多大的字节数)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
(四)、野指针
野指针:指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
    ① 指针未初始化
int main()
{ 
    //局部变量指针未初始化,默认为随机值
    int *p;//没有指向对应的地址空间,无法查找地址
    *p = 20;

    return 0;
}

    ②指针越界访问

int main()
{
    int arr[10] = {0};
    int *p = arr;

    for(int i=0; i<=11; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }

    return 0;
}

    ③指针指向的空间释放

在堆里开辟内存的空间,如果被回收就不能在使用,因此指针就不能再指向那块空间的地址

#如何规避野指针

1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置 NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
(五)、 指针运算
    ①指针  + -  整数
指针的类型决定 + - 整数,指针变量所偏移的空间大小
int main()
{

    float arr[5];
    //指针+-整数;指针的关系运算
    for (float *p = &arr[0]; vp < &arr[5];)
    {
         //后置++,先给数组赋值在偏移四个字节
         *p++ = 0;
    }
    return 0;

}
    ②指针 - 指针
//模拟实现strlen
int my_strlen(char *s)
{
       char *p = s;

       while(*p != '\0' )
              p++;
        
       return p-s;//两个指针之间的空间个数
}

③指针的关系运算
    允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
#define N_VALUES 5
int main()
{
    
    float values[N_VALUES];
    float *vp;
    
    //不能让数组第一个元素的地址与数组第一个元素之前的地址进行比较
    for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
    {
        *vp = 0;
    }

    return 0;
}
(六)、二级指针
    指针变量也是一个变量,每一个变量都会存放到内存中,也就会有地址。二级指针,就是用来存放一级指针变量地址的变量。因此,存放一级指针变量地址的指针为二级指针,存放二级指针变量地址的指针为三级指针,以此类推。

(七)、指针和数组

     如上图所示:可见数组名和数组首元素的地址是相同的。 因为数组是一块连续的空间,所以可以用指针指向数组首元素的地址,通过指针的加减来访问数组的每一个空间。

二、字符指针

     将字符变量的地址放到字符指针中称为字符指针。一种比较特殊的字符指针指向的是常量池中字符串首字符的地址,常量池当中的字符串是不能修改的。而将字符串放到数组当中进行存储的值是可以修改的。

char a = ‘w’;

char* pa = &a;

char* p = "abcdef";

备注:

const加在*前表示不能修改指针所指向空间的值,const加在*后表示不能修改指针的指向

int main()
{

	char* pa = "abcdef";
	printf("%s\n", pa);
	printf("abcdef");
	printf(pa);//pa指向的是"abcdef"的首地址和printf直接打印字符串相同
	printf("\n");

	char arr[20] = "abcdef";
	const char* pb = arr;
	//*pb = 'w'; (×)
	//用const修饰在*号前就不能修改指针所指向的内容
	char* const pbb = "abcdefg";
	//pbb = "bcdefgh";(×)
	//用const修饰在*号后就不能修改指针的指向

	char* const pc = arr;
	//pc = "bcdef";(×)
	*pc = 'w';//改的是arr数组首元素的值
	//用const修饰在*号后就不能修改指针的指向,但可以改变其所指向地址元素的内容
	printf("%s\n", pc);

	//注意
	char* const pd = "abcdef";
	//*pd = 'w';//虽然编译时没有报错,但是常量字符串是在常量池里的,其内容不能被修改,运行时系统会报错
    
    return 0;
}

字符串数组和常量字符串的区别:

int main()
{
	//用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块
	//str1,str2字符串数组存放的是对应字符的asc码值
	//值虽然相同但是他们是两个不同数组,开辟的地址也是不同的
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	
	//字符指针str3,str4存放的是字符串常量池的字符,存放的都是字符'h'的地址
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");


	return 0;
}

运行结果:

三、指针数组

                                                整型数组 - 存放整型的数组
                                                字符数组 - 存放字符的数组
                                                指针数组 - 存放指针(地址)的数组
    指针数组是存放指针的数组,每种类型的数据都可以创建数组,但是数组也可以是由指针组成。

    a、存放字符指针的数组

    b、存放数组首地址的指针数组

四、数组指针

(一)、数组指针的定义

               字符指针——存放字符地址的指针——指向字符的指针 char*
               整型指针——存放整型地址的指针——指向整型的指针 int*
               浮点型的指针——存放浮点型地址的指针——指向浮点型的指针 float*  double*
               数组指针——存放数组地址的指针——指向数组的指针
int main()
{

    //char str[5];
	//char (*pc)[5] = &str;
    //字符数组指针

	int arr[10];

	//pa是一个数组指针,pa要先和*结合表示指针
    //再和[10]结合表示的是指针数组
    //剩下的int表示该数组里面存放数据的类型
    //注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

	int (*pa)[10] = &arr;
    //&arr表示的是数组的地址,而不是数组首元素的地址
    //数组的地址需要由一个数组指针来接收
    

	return 0;
}

    数组名和&数组名的区别:

(二)、数组指针的使用

    ①使用一:两种打印数组的方式

     ②使用二:二维数组的使用

//正常的接受二维数组的参数
void print1(int arr[3][4], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

//用数组指针来接受二维数组,表示的是数组的第几行
void print2(int(*p)[4], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{			
			//printf("%d ", (*(p + i))[j]);
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][4] = { {1,2,3,4}, {2,3,4,5} , {3,4,5,6} };
	print1(arr, 3, 4);
    printf("\n");
    
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
	print2(arr, 3, 4);

	return 0;
}

运行结果:

五、数组参数、指针参数

( 一)、一维数组传参
void test(int arr[])//√
{}
void test(int arr[10])//√
{}
void test(int *arr)//√
{}
void test2(int *arr[20])//√
{}
void test2(int **arr)//√
{}
int main()
{

    int arr[10] = {0};
    //传过去一维数组的地址,可以用数组来接
    //因为arr中的每一个元素都是int类型,所以arr数组名就是int*类型,可以用int*指针来接收
    test(arr);

    //arr2数组中每一个元素都是int*类型,可以用该类型的数组来接收
    //也可以用二级指针来接收
    int *arr2[20] = {0};
    test2(arr2);
}

( 二)、二维数组传参

void test(int arr[3][5])//√
{}
void test(int arr[][])//×
{}
void test(int arr[][5])//√
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//×
{}
void test(int* arr[5])//×
{}
void test(int (*arr)[5])//√
{}
void test(int **arr)//×
{}
int main()
{
     int arr[3][5] = {0};
     //二维数组名表示二维数组首元素的地址,而二维数组的是由一个个一维数组组成的
     //取一维数组的地址就是整一个一维数组的地址
     //所以接受二维数组的地址,要么是二维数组,要么是数组指针
     test(arr);
}

(三)、一级指针传参

void test(int* p) {}

int main()
{
	int a = 10;
	int* p = &a;
	int arr[10];

    //接收基本数据类型的数组名
	test(arr);

    //接收基本数据数据类型变量的地址名
	test(&a);

    //接收一级指针
	test(p);
    
	return 0;
}

(四)、二级指针传参

void test(int** p) {}
int main()
{
	int* arr[10];
	int* pp;
	int** ptr = &arr;

    //传二级指针
	test(ptr); 
	//传一级指针的地址
    test(&pp);
    //可以传一个存放一级指针的数组名
	test(arr);

    return 0;
}

(五)、特殊事例

void test(int** pc)
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		//arr[i]-->*(arr+i)--->*(*pc+i)
		//*pc = *(pc+0) = arr
		//*pc是数组首元素的地址
		printf("%d ", *(*pc+i));
	}
	printf("\n");

	//现在pa指向的是整个数组的地址
	int(*pa)[10] = (int (*)[10]) *pc;

    //pb指向的是数组首元素的地址
	int* pb = *pc;

	printf("%p\n", pa);
	printf("%p\n", pa+1);
	printf("%p\n", pb);
	printf("%p\n", pb+1);

}
int main()
{
	int arr[10] = {2,4,6,8,10,12,14,16,18,20};
	int(*p)[10] = &arr;
	//将数组指针的地址放到二级指针中,解引用时除非强制类型转换为数组指针
	//要不然就只是数组首元素的地址
	//因为不管是整个数组的地址还是数组首元素的地址都表示同一个位置
	int** pc = &p;

	test(&p);
	return 0;
}

     整个数组的地址和数组首元素的地址+-整数的结果

六、函数指针

(一)、函数指针的定义与使用

类型名  (*指针变量名)(函数参数)  = &函数名(或者直接写函数名)

//函数指针
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	
	//pf 是一个存放函数地址的指针变量 -  函数指针

	int (*pf)(int, int) = &Add;//可以理解为&Add给pf

	int ret = (*pf)(2,3);//*pf相当于*&Add==Add(仅助于理解,这样符合语法理解而已)
	//其实(*pf)中的*只是一个号摆设,加多少个*都一样(*****pf)也照样可以使用函数
	//但是加上了*号必须要加括号(*pf)

	//&函数名和函数名都是函数的地址
	int (*pf)(int, int) = Add;
	//int ret = Add(2, 3);
	int ret = pf(2, 3);

	printf("%d\n", ret);

	return 0;
}

(二)、有关函数指针的有趣代码

int main()
{
    //代码一
	//该代码是一次函数调用
	//调用0地址处的一个函数
	//首先代码中将0强制类型转换为类型为void (*)()的函数指针
	//然后去调用0地址处的函数
	( *( void (*)() ) 0 )();
	//《C陷阱和缺陷》

    //代码二
	void (* signal( int, void(*)(int) ) )(int);
	//该代码是一次函数的声明
	//声明的函数名字叫signal
	//signal函数的参数有2个,第一个是int类型,第二个是函数指针类型,该函数指针能够指向的那个函数的参数是int
	//返回类型是void
	//signal函数的返回类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回类型是void
	
    //写成这种比较好理解,但是这样写不合法
	//void (*)(int) signal(int, void(*)(int));


    //正常的数据类型重定义
    typedef unsigned int uint;

    //代码二可以写成如下简化形式
	typedef void(*pf_t)(int) ;//函数指针的重定义要加重定义名字写到(*函数指针名)中
	pf_t signal(int, pf_t);

	return 0;
}

七、函数指针数组

(一)、函数指针数组的定义

数组是一个存放相同类型数据的存储空间,前面有讲到指针数组,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。

                    数据类型 ( * 数组名[数值] ) (函数参数);

如:

int ( * parr1 [ 10 ])();
int * parr2 [ 10 ]();

(二)、函数指针数组的使用(转移表)

void menu()
{
	printf("*******************************\n");
	printf("****** 1. add   2. sub    *****\n");
	printf("****** 3. mul   4. div    *****\n");
	printf("****** 0. exit            *****\n");
	printf("*******************************\n");
}

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

//函数指针数组存放上述函数的地址
//转移表--->传一个数,通过这个数找到对应的数组下标来调用对应的函数
int (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input>=1 &&input<=4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);

	return 0;
}

 、指向函数指针数组的指针

指向函数指针数组的指针是一个 指针指向一个数组, 数组的元素都是 函数指针 
void test(const char* str)
{
     printf("%s\n", str);
}
int main()
{
    //函数指针pfun
    void (*pfun)(const char*) = test;

     //函数指针的数组pfunArr
     void (*pfunArr[5])(const char* str);
     pfunArr[0] = test;

     //指向函数指针数组pfunArr的指针ppfunArr
     void (*(*ppfunArr)[5])(const char*) = &pfunArr;

     return 0;
}

九、回调函数

    回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

    代码实例:  

void menu()
{
	printf("*******************************\n");
	printf("****** 1. add   2. sub    *****\n");
	printf("****** 3. mul   4. div    *****\n");
	printf("****** 0. exit            *****\n");
	printf("*******************************\n");
}

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

void calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	
	do 
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

       运行结果:

       图示讲解:

十、深度解析指针和数组

(一)、一维数组

int a [] = { 1 , 2 , 3 , 4 };
printf ( "%d\n" , sizeof ( a ));
printf ( "%d\n" , sizeof ( a + 0 ));
printf ( "%d\n" , sizeof ( * a ));
printf ( "%d\n" , sizeof ( a + 1 ));
printf ( "%d\n" , sizeof ( a [ 1 ]));
printf ( "%d\n" , sizeof ( & a ));
printf ( "%d\n" , sizeof ( *& a ));
printf ( "%d\n" , sizeof ( & a + 1 ));
printf ( "%d\n" , sizeof ( & a [ 0 ]));
printf ( "%d\n" , sizeof ( & a [ 0 ] + 1 ));
int main()
{
	//一维数组
	int a[] = { 1,2,3,4 };//4*4=16
	printf("%d\n", sizeof(a));//16
	printf("%d\n", sizeof(a + 0));//a+0 其实是数组第一个元素的地址,是地址就是4/8字节
	printf("%d\n", sizeof(*a));//*a是数组首元素,计算的是数组首元素的大小,单位是字节,4
	printf("%d\n", sizeof(a + 1));//a+1是第二个元素的地址,是地址大小就是4/8
	printf("%d\n", sizeof(a[1]));//a[1]是第二个元素,计算的是第二个元素的大小-4-单位是字节
	printf("%d\n", sizeof(&a));//&a是整个数组的地址,整个数组的地址也是地址,地址的大小就是4/8字节
	//&a---> 类型:int(*)[4]

	printf("%d\n", sizeof(*&a));//&a是数组的地址,*&a就是拿到了数组,*&a--> a,a就是数组名,sizeof(*&a)-->sizeof(a)
	//计算的是整个数组的大小,单位是字节-16

	printf("%d\n", sizeof(&a + 1));//&a是整个数组的地址,&a+1,跳过整个数组,指向数组后边的空间,是一个地址,大小是4/8字节
	printf("%d\n", sizeof(&a[0]));//&a[0]是首元素的地址,计算的是首元素地址的大小,4/8字节
	printf("%d\n", sizeof(&a[0] + 1));//&a[0] + 1是第二个元素的地址,地址的大小就是4/8字节
	return 0;
}

(二)、字符数组

    a、

char arr [] = { 'a' , 'b' , 'c' , 'd' , 'e' , 'f' };
printf ( "%d\n" , sizeof ( arr ));
printf ( "%d\n" , sizeof ( arr + 0 ));
printf ( "%d\n" , sizeof ( * arr ));
printf ( "%d\n" , sizeof ( arr [ 1 ]));
printf ( "%d\n" , sizeof ( & arr ));
printf ( "%d\n" , sizeof ( & arr + 1 ));
printf ( "%d\n" , sizeof ( & arr [ 0 ] + 1 ));
printf ( "%d\n" , strlen ( arr ));
printf ( "%d\n" , strlen ( arr + 0 ));
printf ( "%d\n" , strlen ( * arr ));
printf ( "%d\n" , strlen ( arr [ 1 ]));
printf ( "%d\n" , strlen ( & arr ));
printf ( "%d\n" , strlen ( & arr + 1 ));
printf ( "%d\n" , strlen ( & arr [ 0 ] + 1 ));
int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	//char*
	//char [6]
	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	//printf("%d\n", strlen(*arr));//strlen('a')->strlen(97),非法访问-err
	//printf("%d\n", strlen(arr[1]));//'b'-98,和上面的代码类似,是非法访问 - err
	printf("%d\n", strlen(&arr));//&arr虽然是数组的地址,但是也是从数组起始位置开始的,计算的还是随机值
	//char(*)[6]
	printf("%d\n", strlen(&arr + 1));
	//&arr是数组的地址,&arr+1是跳过整个数组的地址,求字符串长度也是随机值
	printf("%d\n", strlen(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,是'b'的地址,求字符串长度也是随机值

	printf("%d\n", sizeof(arr));//arr单独放在sizeof内部,计算的是整个数组的大小,单位是字节,6
	printf("%d\n", sizeof(arr + 0));//arr + 0是数组首元素的地址,4/8
	printf("%d\n", sizeof(*arr));//*arr是数组的首元素,计算的是首元素的大小-1字节
	printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1字节
	printf("%d\n", sizeof(&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;
}

    b、

char arr [] = "abcdef" ;
printf ( "%d\n" , sizeof ( arr ));
printf ( "%d\n" , sizeof ( arr + 0 ));
printf ( "%d\n" , sizeof ( * arr ));
printf ( "%d\n" , sizeof ( arr [ 1 ]));
printf ( "%d\n" , sizeof ( & arr ));
printf ( "%d\n" , sizeof ( & arr + 1 ));
printf ( "%d\n" , sizeof ( & arr [ 0 ] + 1 ));
printf ( "%d\n" , strlen ( arr ));
printf ( "%d\n" , strlen ( arr + 0 ));
printf ( "%d\n" , strlen ( * arr ));
printf ( "%d\n" , strlen ( arr [ 1 ]));
printf ( "%d\n" , strlen ( & arr ));
printf ( "%d\n" , strlen ( & arr + 1 ));
printf ( "%d\n" , strlen ( & arr [ 0 ] + 1 ));
int main()
{
	char arr[] = "abcdef";//数组是7个元素
	//[a b c d e f \0]
	printf("%d\n", strlen(arr));//6,arr是数组首元素的地址,strlen从首元素的地址开始统计\0之前出现的字符个数,是6
	printf("%d\n", strlen(arr + 0));//arr + 0是数组首元素的地址,同第一个,结果是6
	printf("%d\n", strlen(*arr));//*arr是'a',是97,传给strlen是一个非法的地址,造成非法访问
	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//&arr + 1是跳过数组后的地址,统计字符串的长度是随机值
	printf("%d\n", strlen(&arr[0] + 1));//&arr[0]+1是b的地址,从第二个字符往后统计字符串的长度,大小是5

	printf("%d\n", sizeof(arr));//7 - 数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
	printf("%d\n", sizeof(arr + 0));//arr+0是首元素的地址,大小是4/8
	printf("%d\n", sizeof(*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;
}

    c、

char * p = "abcdef" ;
printf ( "%d\n" , sizeof ( p ));
printf ( "%d\n" , sizeof ( p + 1 ));
printf ( "%d\n" , sizeof ( * p ));
printf ( "%d\n" , sizeof ( p [ 0 ]));
printf ( "%d\n" , sizeof ( & p ));
printf ( "%d\n" , sizeof ( & p + 1 ));
printf ( "%d\n" , sizeof ( & p [ 0 ] + 1 ));
printf ( "%d\n" , strlen ( p ));
printf ( "%d\n" , strlen ( p + 1 ));
printf ( "%d\n" , strlen ( * p ));
printf ( "%d\n" , strlen ( p [ 0 ]));
printf ( "%d\n" , strlen ( & p ));
printf ( "%d\n" , strlen ( & p + 1 ));
printf ( "%d\n" , strlen ( & p [ 0 ] + 1 ));
int main()
{
	const char* p = "abcdef";
	
	printf("%d\n", strlen(p));//6- 求字符串长度
	printf("%d\n", strlen(p + 1));//p + 1是b的地址,求字符串长度就是5
	printf("%d\n", strlen(*p));//err,*p是'a'
	printf("%d\n", strlen(p[0]));//err - 同上一个
	printf("%d\n", strlen(&p));//&p拿到的是p这个指针变量的起始地址,从这里开始求字符串长度完全是随机值
	printf("%d\n", strlen(&p + 1));//&p+1是跳过p变量的地址,从这里开始求字符串长度也是随机值
	printf("%d\n", strlen(&p[0] + 1));//&p[0] + 1是b的地址,从b的地址向后数字符串的长度是5

	printf("%d\n", sizeof(p));//p是指针变量,大小就是4/8字节
	printf("%d\n", sizeof(p + 1));//p + 1是b的地址,是地址,就是4/8个字节
	printf("%d\n", sizeof(*p));//*p是'a',sizeof(*p)计算的是字符的大小,是1字节
	printf("%d\n", sizeof(p[0]));//p[0]-->*(p+0) --> *p  就同上一个,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));//p[0]就是‘a’,&p[0]就是a的地址,+1,就是b的地址,是地址就是4/8

	return 0;
}

(三)、二维数组

int a [ 3 ][ 4 ] = { 0 };
printf ( "%d\n" , sizeof ( a ));
printf ( "%d\n" , sizeof ( a [ 0 ][ 0 ]));
printf ( "%d\n" , sizeof ( a [ 0 ]));
printf ( "%d\n" , sizeof ( a [ 0 ] + 1 ));
printf ( "%d\n" , sizeof ( * ( a [ 0 ] + 1 )));
printf ( "%d\n" , sizeof ( a + 1 ));
printf ( "%d\n" , sizeof ( * ( a + 1 )));
printf ( "%d\n" , sizeof ( & a [ 0 ] + 1 ));
printf ( "%d\n" , sizeof ( * ( & a [ 0 ] + 1 )));
printf ( "%d\n" , sizeof ( * a ));
printf ( "%d\n" , sizeof ( a [ 3 ]));
int main()
{
	//二维数组
	int a[3][4] = { 0 };
	//printf("%p\n", &a[0][0]);
	//printf("%p\n", a[0]+1);


	printf("%d\n", sizeof(a));//48 = 3*4*4
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%d\n", sizeof(a[0]));//a[0]是第一行的数组名,数组名单独放在sizeof内部,计算的就是数组(第一行)的大小,16个字节
	printf("%d\n", sizeof(a[0] + 1));//a[0]作为第一行的数组名,没有单独放在sizeof内部,没有取地址,表示的就是数组首元素的地址
	//那就是a[0][0]的地址,a[0]+1就是第一行第二个元素的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*(a[0] + 1)));//*(a[0] + 1)是第一行第2个元素,计算的是元素的大小-4个字节
	printf("%d\n", sizeof(a + 1));//a是二维数组的数组名,数组名表示首元素的地址,就是第一行的地址,a+1就是第二行的地址
	//第二行的地址也是地址,是地址就是4/8   
	//a - int (*)[4]
	//a+1--> int(*)[4]
	printf("%d\n", sizeof(*(a + 1)));//a+1是第二行的地址,*(a+1)表示的就是第二行,*(a+1)--a[1]  //16
	printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,&a[0]+1是第二行的地址,地址的大小就是4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));//*(&a[0] + 1) 是对第二行的地址解引用,得到的就是第二行,计算的就是第二行的大小
	printf("%d\n", sizeof(*a));//a表示首元素的地址,就是第一行的地址,*a就是第一行,计算的就是第一行的大小
	//*a -- *(a+0)--a[0]
	printf("%d\n", sizeof(a[3]));//16字节 int[4]
	//如果数组存在第四行,a[3]就是第四行的数组名,数组名单独放在sizeof内部,计算的是第四行的大小

	int a = 10;
	printf("%d\n", sizeof(int));

	return 0;
}

总结:

    本篇文章将C语言指针相关知识做了详细的介绍,制作不易,希望有需要这方面知识的小伙伴们看了本篇文章有所收获,谢谢!

  • 18
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值