C语言指针深入讲解(一)

目录

1. 字符指针

2. 指针数组

3. 数组指针

4. 数组传参和指针传参


ok,兄弟们,今天咱们来搞指针,指针这个东西,就好比二战时期的加兰德,他很强大,但却需要很长的时间来磨合。

我们先从一些基础的概念开始。

1.指针是一个变量,是用来存放地址的。

2.不同类型的指针大小在同一平台下是固定的,常用的是32位平台4个字节,64位八个字节。
3.指针变量的类型的意义是确定了指针的权限大小,比如+-整数是跳过的字节,以解引用时访问的字节,所以我们可以在一些特定的情况来灵活的运用这些东西来达到一些目的。

4.指针之间的运算(这个大伙去看一下别人的博客吧,我这里就不再啰嗦了)。

。好的,言归正传,现在我们开始逐个深入讲解各个指针 。

1. 字符指针

字符指针,首先第一个问题,字符指针只能用来指向字符吗?答案固然是否定的,就像我们上面提到的,指针的类型只决定了指针的权限,所以,我们是可以用字符指针指向其他类型变量的,但这样做的前提是,他能安全且正确的达到你的目的。

下面,我们来看一串代码了解一下字符指针最基础的用法。

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}

这个就很明显了,让pc这个指针指向字符类型变量ch的地址,也就是指向了字符'w'的地址。

当然这是一种最简单的用法,下面,我们继续来看一手另外的用法。

首先我们看一下下面的代码并且思考一下其中的问题。

int main()
{
    const char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0;
}

 这里到底是存了个什么呢?我们不妨测试一手。

#include<stdio.h>
int main()
{
	const char* pstr = "hello world.";
	printf("%s\n", pstr);
	printf("%c\n", *pstr);
	return 0;
}

 哦吼,现在好像有一点头绪了,我们继续举几个栗子来试一下。

#include<stdio.h>
int main()
{
	char arr[] = "hello azaz_plus";
	printf("%s\n", arr);
    printf("%c\n", *arr);
	return 0;
}

让我们接着来看一下会输出什么 。

 

好的,现在已经云开见月明了,首先,我们知道的数组名表示的是数组首元素的地址,所以我们对他解引用就得到了首元素,对此类比我们就能很轻松的发现,在用字符指针存储一个字符串的时候,其实存储的是一个字符串首元素的地址,这一点是跟数组一样的。

然后我们再提一嘴。

const char* pstr = "hello world.";

为什么这一串代码要用const修饰呢?

其实是为了安全考虑的,因为后面的字符串是一个常量,而字符指针却是一个变量,如果我们一个不小心改动了他的值就会出错,所以要加一个const修饰,使它变成常变量,这样的话会提醒我们这是一个常变量无法被修改的那种。

ok,既然如此,我们就再继续深入一下,我们还是从一段代码开始。

#include <stdio.h>
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");
	}
	if (str3 == str4)
	{
		printf("str3 and str4 are same\n");
	}	
	else
	{
		printf("str3 and str4 are not same\n");
	}
	return 0;
}

 话不多说,我们直接上结果。

这是为啥呢,其实原理很简单,我们不妨调试一手,看一下他们的地址

 

通过调试我们发现,这俩东西的地址是一样的,也就是说明,str3与str4指向的是同一块内存,同理我们可以用相同的方法的刀str1与str2的地址,然后这个我推荐大家自己去试一下,所以,我们就可以得出一个结论。

这里 str3 str4 指向的是一个同一个常量字符串。 C/C++ 会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1 str2 不同, str3 str4 不同。

 ok,字符指针我们就先讨论到这里,当然关于字符指针还有很多东西,这个在我日后的博客中会慢慢的进行应用和讲解。

2. 指针数组

指针数组,这东西很明显是个数组,就跟整形数组差不多,整形数组存放整形,字符数组存放字符,所以说指针数组是存放指针的,下面我们通过代码来展示一下。

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

 这个很简单,我们就不再做过多的讨论,等以后应用的时候,我们以实际栗子来展示一下他的用法,现在只需要了解即可。

3. 数组指针

ok,兄弟们,在学习数组指针之前,我们要先清楚,数组指针到底是指针还是数组?

我们不妨先看一下另一个问题,字符指针是字符还是指针,答案很明显是指针啊,所以说数组指针是一个指针。

下面的话,我们先看一下C的符号优先级

通过这个表,我们不难发现,方括号( [ ] )的优先级要大于解引用( * ),这一点我们先了解,过一会我们会用到的。

然后我们还是先来看一个代码。

int *p1[10];
int (*p2)[10];

 不难发现,第一个代码就是我们刚才提到的指针数组,他表示的是一个数组,那么第二个代码呢?

这就要用的我们刚才提到的肖芝士了,首先,我们我们先来分析第一个代码,因为[ ]的优先级要高于 * 的优先级,所以p1会先和[10]结合变成数组,然后再跟然后int跟*结合,然后p1就变成了一个元素为int*类型的数组。

那么对于第二个代码,我们还是来慢慢的分析,首先因为*和p2被括号括起来了,所以这两个会优先结合,然后就表示p2这是一个指针,之后int 自然只能跟[10]结合,也就是说,现在的p2是一个指针,他指向的类型是 int [10]类型,而这个类型是一个数组,故第二个代码是一个数组指针,他指向的是一个数组。

由上述分析我们发现,在声明一个数组指针的时候,一定要加括号让*跟p2优先结合,这样声明出来的才是一个数组指针,否则的话,就变成指针数组了。

下面,我们继续对数组指针进行深入研究。

 我们先来看一下数组名取地址数组名的区别。

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
}

首先,我们需要知道的是%p是用来打印地址的,下面来看一下答案。

 

通过答案我们不难发现,arr与&arr的地址是相同的,但是他们表示的含义却不相同。

我们不妨再通过一个代码测试一下。

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

 

通过答案,我们不难发现,在arr+1的时候,地址跳过了4个字节,而在&arr+1的时候,地址却跳过了40个字节,而这正是数组的大小。

所以,在&数组名的时候,此时的数组名表示的是整个数组,我们取出的地址是整个数组的地址,他的值与数组首元素的地址相同,但是表示的意义却不一样,也就是说,此时的&数组名+1要跳过的是整个数组。 

讨论完了一维数组之后,我们来继续讨论一下二维数组。

《C陷阱与缺陷》中这样写道:“C语言只有一维数组,但是,C语言中数组的元素可以是任意类型的对象,当然也可以是数组”。

这句话就已经很明显的说明了一些问题,其实二维数组就是一个元素为一维数组的一维数组。

下面,让我们来模仿一个二维数组。(这里需要用到指针数组)

#include <stdio.h>
int main()
{
	int arr1[2] = { 1,2 };
	int arr2[2] = { 3,4 };
	int* arr[2] = { arr1,arr2 };
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 2; j++)
		{
			printf("%d ", *(arr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

从这里我们可以发现,我们定义的那个指针数组有两个元素,他的没一个元素是另一个数组的数组名,也就是数组第一个元素的地址,那么,二维数组是不是跟我们模拟的这个一样呢?

我们可以通过几个代码来检验一下

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

 

看来结果已经很明显了,所以我们可以总结一下。

对于一个二维数组,其实就是一个每个元素都是一个一维数组的一维数组,也就是说,二维数组的每一行就是一个一维数组,所以我们取出二维数组的某一行的地址的时候,其实得到的是该行的一维数组的首元素的地址。

 好的,然后我们来举个关于数组指针应用的栗子

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int 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 (int 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);
	print_arr2(arr, 3, 5);
	return 0;
}

这段代码展示了,对于二维数组的两种不同的打印方式。

ok,下面我们来几道小题,看一下下面代码都是什么意思。

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

 很明显,第一个是个数组,第二个是个指针数组,第三个是数组指针

我们来详细讲一下第四个, 首先根据优先级的结合顺序, parr3会跟先和[10]结合, 然后剩下了     int (*)[5] ,我们想一下数组指针的,例如arr[5]的数组指针是 int (*p) [5];其中p是变量名,int(*)[5]是p的类型,所以第四个代码表示的就是 parr3是一个数组,数组中有是个元素,每个元素的类型又是一个数组。

就是下面代码所展示的那种。

#include <stdio.h>
int main()
{
	int a[5];
	int(*parr3[10])[5] = { &a };
	return 0;
}

4. 数组传参和指针传参

(这一部分当做小题,我会在之后专门出一篇文章讲解这些题目)

(1)先来看一下一维数组

#include <stdio.h>
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 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

(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);
}

(3)一级指针

#include <stdio.h>
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;
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(p, sz);
	return 0;
}

 当函数的参数为一级指针的时候,可以接收什么参数? 

void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?

(4)二级指针

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

当函数的参数为二级指针的时候,可以接收什么参数? 

void test(char **p)
{
 
}
int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);
 return 0;
}

 

 

 

 

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值