【C语言】指针详细讲解(1)

       在学习这篇指针详细讲解之前,我们需要先理解内存,地址和指针的关系。可以看之前发过的一篇初识指针篇,链接如下:https://blog.csdn.net/2301_79755897/article/details/139602535?spm=1001.2014.3001.5501

一、指针的类型

        在我们学习变量时变量的类型有int、char、short、long等类型。那指针都有什么类型呢?他们代表什么呢?

int * p表示指针p指向的数据类型是int类型
char * p表示指针p指向的数据类型是char类型
short * p表示指针p指向的数据类型是short类型
long * p表示指针p指向的数据类型是long类型
float * p表示指针p指向的数据类型是float类型
double * p表示指针p指向的数据类型是double类型

        通俗易懂的讲就是int *类型的指针,是为了存放int类型数据的地址。

        这里注意:在x86环境下,不论什么类型的指针大小都是4个字节。在x64环境下,不论什么类型指针都是8个字节!!


二、指针+-整型

        既然我们了解了指针类型,那么不同类型的指针有着什么样的意义呢?

看上面代码打印的结果我放在了右边的方框里了,下面我们来分析一下为什么会产生这样的结果。

#include<stdio.h>

int main() 
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;

	printf("%p\n", &n);//00B9FB18
	//这里打印的就是整型变量n的地址没什么可讲的

	printf("%p\n", pc);//00B9FB18
	//我们将n取地址强制转换成char*类型赋值给了pc
	//就是将n的地址给了字符指针pc
	//所以这里打印出来的pc与n的地址一样

	printf("%p\n", pc + 1); //00B9FB19
	//为什么这里是00B9FB19
	//首先指针pc指向的是char数据类型,因为char类型的字节大小是一个字节
	// 在这里代表的是跳过一个char类型数据
	//所以在给pc+1时加的是一个字节,即地址+1
	//从00B9FB18->00B9FB19

	printf("%p\n", pi);//00B9FB18
	//这里和是将n的地址赋值给了整型指针pi中,所以结果是n的地址

	printf("%p\n", pi + 1);//00B9FB1C
	//首先指针pi指向的是整型数据类型,整形数据类型在内存占据4个字节
	//所以在这里加1代表的是跳过一个整型数据
	//00B9FB1C-00B9FB18=4个字节
				
	return 0;
}


三、野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

       在局部未初始化时,运行过程中系统会报错,所以在我们写代码的时候应该注意这些问题。

      这个就是数组越界访问形成了野指针,破环了栈堆。

如何避免野指针的形成?
   1. 指针初始化
   2. 小心指针越界
   3. 指针指向空间释放即使置 NULL
   4. 避免返回局部变量的地址
   5. 指针使用之前检查有效性

四、字符指针 

int main()
{
	//有单个字符和字符串指针
	//那么指针指向的是什么呢?
	char a = 'w';
	char a1[] = "hello word";

	char* ap = &a;//这里ap指向的是a的地址
	char* a1p = &a1;//这里a1是一个字符串(字符数组),指向的是字符串首元素地址

	return 0;
}

下面有一道大家试试看

int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";

	const char* str3 = "hello world.";
	const char* str4 = "hello world.";

	if (str1 == str2)//数组名代表的是首元素地址
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	//因为str1和str2是两个不同的字符数组,所以他们首元素地址肯定不一样

	if (str3 == str4)//str3和str4代表的是他们指向的地址
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	//str3和str4指向的是同一个字符串,所以首元素是地址是一样的

	return 0;
}


五、指针数组

我们学习过整型数组,字符数组,浮点型数组,那指针数组是什么呢?(简单理解,存放指针的数组)当然是存放指针的数组,那怎么定义呢?

int main()
{
	int* p[10] = { 0 };
	//这是一个指针数组
	//int* 表示的是类型,那就是整型指针
	//p    表示的是数组名
	//[10] 表示的是数组元素个数
	//整体的表示意思是,一个名为p的数组里放了10个类型为整型指针的数组

	//下面的依次类推
	short* p1[10] = { 0 };
	float* p1[10] = { 0 };
	double* p1[10] = { 0 };

	return 0;
}

六、数组指针

上面我们刚讲了指针数组这里就来了数组指针,下面我们先从定义了解数组指针。

首先我们需要思考数组指针到底是数组还是指针呢?

int main()
{
	//对于指针来说
	//整型指针指向的是整型数据
	//字符指针指向的是字符数据
	//short指针指向的是short数据
	//以此类推

	//那数组指针指向的就是一个数组
	//那该如何定义一个数组指针?
	//数据类型(*指针变量名)[数组长度];

	int(*p)[10] = { 0 };//指针p就是一个指向数组的数组指针
	//int [10]={0}我们先把(*p)去掉
	//这里我们能看到的是一个没有数组名的数组

	int* p1[10] = { 0 };//这是一个指针数组
	//为什么要加(),因为()优先级大于*
	//所以在指针数组(int*)p1[10]={0}
	//(int*)表示的是数组内元素类型
	//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

	return 0;
}

数组指针的使用 

void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
			printf("\n");
	}
}
void print_arr2(int(*arr)[5], int row, int col)//这里我们使用的是数组指针来接受传参
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);
	return 0;
}

 这个就是关于数组指针在二维数组的理解


七、&数组名VS数组名

下面我将用代码来讲解

         我将地址打印出来,大家可以发现arr=&arr,但arr+1和&arr+1不同,(&arr+1)-(&arr)=40(十进制)。&arr是取出整个数组地址,地址是首元素地址,但在运算时+-,是前进或者后退arr整个数组这么大


八、数组传参,指针传参

一维数组传参

void test(int arr[])//ok?      yes
{}
void test(int arr[10])//ok?    yes
{}
void test(int* arr)//ok?       yes
{}//这里定义的是一个arr指针,因为传过来的是一个地址,所以用指针接受
void test2(int* arr[20])//ok?  yes
{}
void test2(int** arr)//ok?     yes
{}// 传过来的是arr2首元素地址
  // 这个元素是一级指针,所以用二级指针来接收 
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

二维数组传参

void test(int arr[3][5])//ok? YES
{}
void test(int arr[][])//ok?   NO
{}
void test(int arr[][5])//ok?  YES
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?      NO
{}//在二维数组传参时,传来的时首行元素的整体地址。
  //一级指针不能接收这个整体的地址
void test(int* arr[5])//ok? NO
{}//形参的类型为一个指针数组,我们传过来的是二维数组
void test(int(*arr)[5])//ok?  YES
{}
void test(int** arr)//ok?     NO
{}// 二级指针存放的是一级指针的地址
  // 传过来的是arr二维数组的首行地址,并不是一个一级指针的地址
  // 所以不行
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	//注意二维数组名表示的是首行数组地址,而不是第一行第一列元素地址
}

一级指针传参

void print(int* p, int sz)//接收地址的话,我们需要用指针来接受
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;//这里是将数组首元素地址赋值给了指针p
	int sz = sizeof(arr) / sizeof(arr[0]);
	
	print(p, sz);//这里p存放的是数组首元素的地址
	return 0;
}

二级指针传参

void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);//这里二级指针pp存放的是一级指针的地址
	test(&p);//这是一级指针的地址,接收这个参数需要二级指针
	//pp=&p
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值