前言

本章介绍指针进阶上半部分。

1、 字符指针

2、 数组指针

3、 指针数组

4、 数组传参和指针传参


在讲解指针进阶前我们先复习一下,指针的一些基本概念:

(1) 指针是一个变量,用来存放地址,这个指针唯一标识一块内存空间。


(2) 指针的大小(所占的空间的大小)固定是4/8个字节,在32/64位环境下会有所差异。


(3) 指针是有不同类型的,指针的类型决定了它解引用时访问多大的存储空间。


(4) 指针的类型也决定了,指针+/-整数会跳过多少个字节(或多大一块空间),指针-指针,得到的是两个指向同一块空间的指针之间的元素的个数,以及指针的关系运算。


详细的介绍请查看我在《指针初阶》中的介绍,

TP:指针初阶_醉酒笑清风的技术博客_51CTO博客



一、 字符指针

1、字符指针定义

字符指针顾名思义,我们要对字符进行取地址,存放在一个指针变量中,这个存放了字符地址的指针变量就叫字符指针。

2、字符指针用法

📕举例:           

int main()
{
			char ch = 'c';
			char* pc = &ch;
			*pc = 'd';
			printf("%c\n", ch);
			return 0;
}

上述代码,我们可以看到,我们定义了一个字符变量ch,存放了个字符c,又对ch变量取地址,将地址存放在指针变量pc中,此时的pc就是一个字符指针,它的类型是char*。

指针进阶(上)_数组指针

所以,当对*pc进行修改时,ch的中的内容也会被修改。

🔑运行结果:

指针进阶(上)_数组_02

字符指针还有一种表示形式,如下

📕举例:

int main()
{
			char* str = "hello world!";
			printf("%s\n", str);
			return 0;
}

这里的str存放的是hello world!这个字符串的首字符的地址,而不是把整个字符串存放在里面。

指针进阶(上)_数组指针_03

然后通过首地址逐渐向后打印,直到找到字符串中的\0,停止打印,得到的就是hello world!这个字符串。

3、字符指针练习

一道经典的面试题:

#include <stdio.h>
int main()
{
		char str1[] = "hello world.";
		char str2[] = "hello world.";
		char* str3 = "hello world.";
		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;
}

🔑运行结果:

指针进阶(上)_数组名_04

为什么会是这样的结果?这里我们要知道一个概念什么叫常量字符串,常量字符串指的是在程序中声明时就被确定下来,并且不能被修改的字符串。

解析:

因为用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,用来存放"hello world.",因为开辟了不同的空间所以他俩的起始位置也是不同的,起始位置不同他俩的值就不可能相同,所以str1不等于str2。

因为"hello world.",本身就是一个常量字符串,不能被修改,既然不能被修改的话,内存中就只存储这一份,而str3和str4两个字符指针也就指向这块空间的同一起始位置,他俩的值也就相同,所以str3等于str4。

指针进阶(上)_数组_05



二、 指针数组

1、 指针数组的定义

整型数组中存放的是整形变量,字符指针中存放的是字符变量,那指针数组中存放的就是指针变量。

指针进阶(上)_数组指针_06

int*  arr[10]可以这样拆分了理解:因为[]的优先级高于*,所以数组名优先与[]结合,说明arr[10]是一个数组,这个数组中元素的类型是int*类型的。

2、 指针数组的使用

指针数组在一维数组中的使用(这种几乎没有人这样用,这里写仅代表它可以用于一维数组)     

📕举例:           

#include <stdio.h>
int main()
{
				int a = 10;
				int b = 20;
				int c = 30;
				int d = 40;
				int e = 50;
				int* arr[5] = { &a,&b,&c,&d,&e };
				int i = 0;
				for (i = 0; i < 5; i++)
				{
					printf("%d ", *(arr[i]));
				}

				return 0;
}

上述代码使用指针,去访问变量a~e的地址,再解引用得到a~e变量的数值。(简称:脱裤子放屁)

但是指针数组可以应用在二维数组中。

📕举例:

#include <stdio.h>
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");

				}

				return 0;
}

🔑运行结果:

指针进阶(上)_数组_07

  上述代码可以理解为:创建了三个一维数组,但是输出是以类似于二维数组输出方法输出的。  

指针进阶(上)_数组指针_08

parr中存放了arr1~arr3,三个数组首元素的地址,即通过数组首元素找到对应的数组。

指针进阶(上)_数组指针_09

这种输出找元素输出的方式类似于输出二维数组的输出方式。



三、 数组指针

1、 数组指针的介绍

整形指针int* pa;是能够指向整形的指针,浮点型指针float* pf;是能够指向浮点型的指针。那数组指针就是能够指向数组的指针。

所以,数组指针是一个指针,它是一个指向数组的指针,而指针数组是一个数组,它是一个存放指针的数组。

2、 数组指针如何定义?

  我们先来剖析指针数组,指针数组:int* arr[10],因为数组名先与[]结合,说明他是一个数组,数组中每个元素的类型是int*类型。

那怎么写数组指针呢?数组指针的定义方式为:int (*arr)[10],因为在表达式中,()的优先级最高,所以*和数组名arr先结合,说明这是一个指针,(*arr)后面的[10],说明这个指针指向的数组有10个元素,再往前看int (*arr),说明数组的每个元素的类型是int类型。

int* arr1[10] ——> arr1是一个指针数组,数组有10个空间,每个空间中存放的是一个int*。

int (*arr2)[10] ——> arr2是一个数组指针,这个指针指向的数组有10个元素,每个元素的类型是int类型。

📕举例:

int arr[10] = {0};
int (*arr2)[10] = &arr;


3、 &数组名VS数组名

数组名代表数组首元素的地址,但是有两个例外:

(1)sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节。

(2)&数组名,代表取出的是整个数组的地址。

指针进阶(上)_数组_10

不管是直接取数组名还是数组首元素的地址,再或者是取整个数组的地址,他们仨的地址是相同的,这里只能说明arr,arr[0],&arr的值是一样的,但是类型是不一样的。

指针进阶(上)_数组名_11


4、  数组指针的使用

一般情况下,一维数组不适用数组指针,因为用起来怪怪的,很别扭。以下代码仅供参考。

📕举例:

#include <stdio.h>
int main()
{
				int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
				int(*parr)[10] = &arr;

				int i = 0;
				for (i = 0; i < 10; i++)
				{
					printf("%d ", *(*parr + i));
				}

				return 0;
}

代码中的难点在*(*parr + i)上,我们可以这样理解,我们把数组arr的地址存放在数组指针parr中,所以我们对parr解引用得到的就是整个数组,另外数组名也相当于整个数组,所以*parr也相当于拿到了这个数组的数组名。接下来就和指针的计算是一样的了。

📕举例: 

#include <stdio.h>
int main()
{
				int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
				int* parr = arr;

				int i = 0;
				for (i = 0; i < 10; i++)
				{
					printf("%d ", *(parr + i));
				}

				return 0;
}

数组指针大多数情况下应用于二维数组中。

📕举例:

void Print1(int parr[3][5], int row, int col)
{
			int i = 0;
			for (i = 0; i < row; i++)
			{
					int j = 0;
					for (j = 0; j < col; j++)
					{
						printf("%d ", parr[i][j]);
					}
					printf("\n");
			}
}


void Print2(int(*parr)[5], int row, int col)
{
			int i = 0;
			for (i = 0; i < row; i++)
			{
					int j = 0;
					for (j = 0; j < col; j++)
					{
						printf("%d ", *(*(parr + i) + j));
									//*(parr[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} };
			//Print1(arr, 3, 5);
			Print2(arr, 3, 5);

			return 0;
}

🔑运行结果:

指针进阶(上)_数组名_12

💡解析:

(1)  Print1函数采用的是数组传递数组接收的方式,Print2采用的是数组传递,数组指针接收的方式。


(2)  Print2函数为什么会得到这样的结果呢?因为在二维数组中二维数组的首元素是第一行数组。


(3)  所以代码中的*(parr+i),根据i的变化,数组改变的是数组指针指向的“一维数组”发生了变化。i=0,就是{1,2,3,4,5}这个数组,i=1,就是。。。。。


(4)  *(parr+i)确定了是哪个“一维数组”,而*(*(parr+i)+j)中+的j就是指这个“一维数组”中的哪个元素。

指针进阶(上)_数组指针_13


5、 二维数组访问元素的几种方法

printf("%d ", *(*(parr + i) + j));
// printf("%d ", (*(parr + i))[j]);
// printf("%d ", *(parr[i] + j));
// printf("%d ", parr[i][j]);

以上方法均等价,第四种是最常见的写法,但是不管咱们怎么写,操作系统都认为咱们写的是第一种。


6、 拓展

int main()
{
		int arr[5];
		int* parr1[5];
		int(*parr2)[5];
		int(*parr3[3])[5];

		return 0;
}

💡解析:

(1)  第一个int arr[5],代表的是一个数组,有5个元素。每个元素的类型是int类型,所以第一个是一个整型数组。


(2)  因为[]的优先级高,所以parr1与[]先结合说明是一个数组,这个数组有5个元素,每个元素的类型是int*类型,所以第二个是一个指针数组。


(3)  因为()优先级高,所以*先与parr2结合,说明这是一个指针,这个指针指向一个有10个元素的数组,数组的每个元素是int类型的,所以第三个是一个数组指针。


(4)  因为[]的优先级高,所以parr3与[3]结合,说明他是一个数组,这个数组有3个元素,每个元素的类型是int(*)[5],这个int(*)[5]是什么,他不就是数组指针嘛,只是把数组名去掉了。所以结合起来parr3是一个存放数组指针的数组。


四、  数组传参和指针传参

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

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

答:上述都合理。

💡解析:

指针进阶(上)_数组_14


2、 二维数组传参

📕举例:

void test(int arr[3][5])//合理不?
{}

void test(int arr[][])//合理不?
{}

void test(int arr[][5])//合理不?
{}

int main()
{
		int arr1[3][5] = { 0 };
		int arr2[3][] = { 0 };
		int arr3[][] = { 0 };
}

答:第一条和第三条合理,第二条不合理。

💡解析:

指针进阶(上)_数组名_15

🔺总结:二维数组可以省略行,但不能省略列。因为对于二维数组来说,它在内存中其实是连续存放的,如果不指定每个数组中有几个元素,那这个数组也没法确定,就比如,如果不指定每列有几个元素的话,那这个数组就有歧义了,每列是圈1个元素、还是圈2个元素、还是圈3呢?

指针进阶(上)_数组指针_16


📕举例:

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

答:只有第三条合理,其余均不合理。

💡解析:

指针进阶(上)_数组指针_17


3、  一级指针传参

📕举例:一级指针传参,一级指针接收

#include <stdio.h>
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(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;
}

🔑运行结果:

指针进阶(上)_数组_18

❓思考:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

#include <stdio.h>

void test1(int* p)
{}

void test2(int* p)
{}

int main()
{
    int a = 5;
    int* p = &a;
    int arr1[5];
    test1(&a);//取出整形变量a的地址传递给指针p,√
    test1(p);//将a的地址取出存放在指针变量p中,再把p传递给形参p,√
    test1(arr1);//数组名是首元素的地址,首元素的地址传递给形参p,√

    char c = 'w';
    char* pc = &c;
    char arr2[5];
    test2(&c);
    test2(pc);
    test2(arr2);
    //test2函数传参的解析和test1大同小异,这里就不多作介绍了。
    return 0;
}


4、  二级指针传参

📕举例:

#include <stdio.h>

void test(int** ppb)
{
	printf("num = %d\n", *(*ppb));
}

int main()
{
		int a = 5;
		int* pa = &a;//一级指针
		int* ppa = &pa;//二级指针
		test(&pa);
		test(ppa);

		return 0;
}

🔑运行结果:

指针进阶(上)_数组指针_19

❓思考:当一个函数的参数部分为二级指针的时候,函数能接收什么参数?

#include <stdio.h>

void test(int** ppb)
{}

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

	test(&pa);//将一级指针取地址传递给二级指针ppb,√
	test(ppa);//将一级指针的地址存放在二级指针ppa中,二级指针传参,二级指针接收,√
	test(arr);//arr是一个指针数组,数组中的元素是指针,而数组名又是数组首元素的地址,
			  //实参传递指针数组的首地址,形参用二级指针接收,√

	return 0;
}


结尾:感谢你能看到这里,由于作者水平有限,如有错误请不吝赐教,谢谢啦!

指针进阶(上)_数组指针_20