【C语言】详解指针的初阶和进阶以及注意点(1)

文章目录

    • 一、必备的指针基础
    • 二、指针进阶
        • 1、字符指针
        • 2、指针数组和数组指针
        • 3、函数参数和指针参数
        • 4、函数指针
        • 5、总结

一、必备的指针基础

什么是指针?
指针理解的两点:

  1. 指针是内存中的一个最小单元编号,也就是地址
  2. 口头说的指针,通常是指针变量,这个变量存放内存地址

所以指针是地址,口头说的指针是指指针变量。
如:

*说明p是一个指针,指针变量p存放a的地址,所以p的值是地址,类型是int*
int a=10;
int *p=&a;

这是最开始,之后会有其它很复杂的指针,理解p是什么很重要。


指针的大小多大?怎么来的?
【C语言】详解指针的初阶和进阶以及注意点(1)_第1张图片
所以,对于在32位平台下

#include
int main()
{
	printf("%d\n", sizeof(int*)); //4
	printf("%d\n", sizeof(char*)); //4
	printf("%d\n", sizeof(float*)); //4
	return 0;
}



指针类型的意义是什么?
下面来看这样一个例子

一个指针指向一个char类型变量的地址,指针+1指向下一个char类型变量的地址,char类型大小一个字节对应地址+1,而int类型就是4个字节,所以地址+4。(注意这里+1和地址本身大小4/8字节无关,别搞混了)

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

	printf("%p\n", &n); //006FFBB8
	printf("%p\n", pc); //006FFBB8
	printf("%p\n", pi); //006FFBB8
	printf("%p\n", pc + 1); //006FFBB9
	printf("%p\n", pi + 1); //006FFBBC
	return 0;
}

可知不同类型的指针,在指针移动的距离上,取决于指针指向的地址类型。


指针的解引用
*表示解引用操作符
第一种情况 这种情况" * "的意思是表示pa是一个指针

int a=10;
char* pa=&a;

第二种情况 这种情况" * "表示对指针pa进行解引用,将pa指针变量储存的地址解引用,表示对应地址的变量本身。

int a=10;
char* pa=&a;
*pa=20; 

看下面这个例子
对char * 类型的指针解引用只访问1个字节,对int * 类型的指针解引用访问4个字节。

int main()
{
	int n = 0x11223344;
	printf("%x\n", n);//11223344
	char* pc = (char*)&n;
	*pc = 0;
	printf("%x\n", n);//11223300
	int* pi = &n;
	*pi = 0;
	printf("%x\n", n);//0
	return 0;
}

指针的类型也决定了,对指针解引用的时候能操作几个字节
为什么是 *pc=0后是11223300,而不是00223344,可以看看小端与大端存储

野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的)

#include
int main()
{
	int* p; //局部变量未初始化,值为0xcccccccc,随机值
	*p = 20; //未初始化,不能解引用,程序报错
	return 0;
}


二、指针进阶

1、字符指针

先来以下代码

int main()
{
	//指针指向单个字符,pc存ch地址
	char ch = 'w';
	char* pc = &ch;
	*pc = 'b';
	printf("%c\n", ch);//b

	//把首字符a的地址赋值给了p,常量字符串不可改变所以加个const
	const char* p = "abcdef";
	char arr[] = "abcdef";
	//*p = 'w';//err
	*arr = 'w';//首元素a->w

	//遇到‘\0’终止打印
	printf("%s\n", p);//abcdef
	printf("%s\n", arr);//wbcdef
	return 0;
}

值得注意的是,对于字符串,指针存的只有首字符的地址。

一道经典题:

int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	//p1==p2 因为p1和p2都存的是字符a的地址,
	//而常量字符串放在只读内存中(只读不写),就没必要存在多份一样的
	if (p1 == p2) 
		printf("p1==p2\n");
	else
		printf("p1!=p2\n");

	//需要创建两个不同的数组 首元素的地址不一样
	if (arr1 == arr2)
		printf("arr1==arr2\n");
	else
		printf("arr1!=arr2\n");

	return 0;
}


2、指针数组和数组指针

指针数组:数组里面存的是指针
通常是以下形式

//[]优先级大于*,所以先是arr1[5]数组,数组里面数据类型是int*
//arr1类型是int* [5]
int* arr1[5];  

//arr2[5]数组,数组里面数据类型是int**
int** arr2[5];

用指针数组存入其它数组的首地址,打印二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* parr[3] = { arr1,arr2,arr3 };

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(parr + i) + j));
		}
		printf("\n");
	}
}



数组指针:指针指向数组
通常是以下形式

//指针parr1指向一个数组,数组里面有5个数据,数据类型为int。
//parr1类型是int (*)[5]
int (*parr1)[5];

//指针parr2指向一个数组,数组里面有5个数据,数据类型为int*
//parr2类型是int* (*)[5]
int* (*parr2)[5];

数组指针一般存储的地址是整个数组地址
注意:数组名的两个特例

int main()
{
	int arr[10] = { 0 };
	int (*p)[10] = &arr;
	
	//数组名代表首元素地址
	printf("%p\n", arr); //006FF834
	printf("%p\n", arr + 1); //006FF838
	
	//首元素地址
	printf("%p\n", &arr[0]); //006FF834
	printf("%p\n", &arr[0] + 1); //006FF838

	//数组地址代表的是整个数组的地址
	printf("%p\n", &arr); //006FF834
	printf("%p\n", &arr + 1); //006FF85C

	//sizeof(arr) 只有数组名的时候代表整个数组大小,注意是只有
	int sz = sizeof(arr);
	//这时就代表了地址大小
	int sz1 = sizeof(arr+0);
	printf("%d\n", sz);//40
	printf("%d\n", sz1);//4
	return 0;
}

1.sizeof(数组名) 整个数组的大小
2.&数组名,这里的数组名表示的依然是整个数组,&数组名表示整个数组的地址


数组指针指向数组,那么数组指针应该储存的是数组地址:

//数组指针p指向二维数组首行数组地址
void print2(int(*p)[5],int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			//p+i选定哪一行,*(p+i)可理解为解引用一维数组地址就成了一维数组名所代表的首元素地址
			printf("%d ", *(*(p + 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};
	print2(arr, 3, 5); //二维数组名表示第一行数组的地址。
	printf("%p\n", arr);//00EFFB18
	printf("%p\n", arr + 1);//00EFFB2C  加20  arr代表第一行数组的地址,+1跨越一个一维数组
	printf("%p\n", *arr + 1);//00EFFB1C 加4 *arr代表第一行数组第一个元素地址,+1跨越一个数
}



让我们来看看数组指针指针数组放在一起。

//先看括号里,arr1[5]代表一个数组,这个数组类型是int (*)[10],这个数组存储5个指针,
//每个指针指向一个含有10个int类型的数组。
int (*parr1[5])[10];  //存放数组指针的数组

3、函数参数和指针参数

1、一维数组传参
一维数组都有以下传参方式

void test(int arr[])
{}
void test(int* arr)
{}
void test(int arr[10])
{}
void test2(int *arr[20])
{}
//指针数组里存的是一级指针,数组名是首元素地址,二级指针存的是一级指针的地址
void test2(int **arr)
{}


int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
	return 0;
}

2、二维数组传参
二维数组值得注意的是,一般数组名表达的是首行一维数组的地址

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); //arr表示第一行一维数组的地址
	return 0;
}

3、一级指针传参
一级指针很简单

void print(int *p)
{}

int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 print(p);
 return 0;
}

4、二级指针传参
记住:二级指针存放的是一级指针的地址

void test(char **p)
{}

int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);//可以
 test(ppc);//可以
 test(arr);//可以 arr代表数组首元素地址,首元素是一级指针
 return 0;
}

4、函数指针

先让我们来看看函数的地址是否可以用平常方式表示。

int test(int x,int y)
{
	printf("\n");
	return x+y;
}

int main()
{
	printf("%p\n", test);//001313FC
	printf("%p\n", &test); //001313FC
	return 0;
}

有了地址,接下来该怎么保存?
首先需要一个储存地址的指针*(pfun)=&test,然后一定需要类型int,最后加上参数,就完成了地址的保存

int *(pfun)(int,int)=&test;

看以下代码

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

int main()
{
	//pf先和*结合,说明pf是指针,指针指向的是一个函数,指向的函数参数为(int, int),返回值类型为int。
	int (*pf)(int, int) = Add; //保存函数地址
	int ret = (*pf)(2, 3); //通过指针访问
	printf("%d", ret);
	return 0;
}

5、总结

本节重点在于,指针变量的理解、一维和二维数组的数组名的注意项、数组指针和指针数组、数组和指针的传参。

指针总体一直是C语言很难的问题,但将其拆分一小个小个的理解就会发现非常简单。

这次的第一节先将指针的基础和进阶中的基础总结完成,下一节进阶内容需要通过总和上面的内容理解,所以理解好这节内容非常重要。

你可能感兴趣的:(C语言基础,c语言)