笔记19-1(C语言进阶 指针进阶)

目录

注:

字符指针

例子(求打印结果 char --- char*)

指针数组

数组指针

知识点

数组指针的使用

应用情景(打印二维数组的每一个元素)

小结

数组参数、指数参数

一维数组传参

二维数组传参

一级指针传参

二级指针传参

总结

指针

数组


注:

 本笔记参考B站up鹏哥C语言的视频


  1. 指针就是给变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型的,指针的类型决定了:①指针的±整数的步长,②指针解引用操作时的权限。

字符指针

有一种指针的类型为字符指针 char*

一般使用:

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

但是,字符指针不仅可以指向字符,还可以指向字符串:

int main()
{
	char* ps = "Hello World";
	printf("%c\n", *ps);
	return 0;
}

这种写法本质上是把这个字符串首字符'H'的地址存入ps内,打印结果是:H。(ps:这种写法区别于数组,一个放入首地址,另一个放入字符串;而且ps是个变量,arr是个数组。不过,访问形式可以一样,都是访问字符串的首地址。)

例子(求打印结果 char --- char*)

题目来自《剑指Offer》

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 no same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are no same\n");

	return 0;
}

打印结果:

str1 and str2 are no same
str3 and str4 are same

解析:

对于 str1 和 str2,str1 和 str2 的首元素地址是不相同的:

笔记19-1(C语言进阶 指针进阶)_第1张图片

对于 str3 和 str4:

注意:str3 和 str4 内存放的"Hello World"是常量字符串,字符串内容无法更改。

譬如:

int main()
{
	char* ps = "Hello World";
	*ps = 'w';
	return 0;
}

开始调试,发生:

笔记19-1(C语言进阶 指针进阶)_第2张图片

--- 代码挂掉了。

结论:相同的字符串在内存中只存一份

笔记19-1(C语言进阶 指针进阶)_第3张图片

指针数组

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

指针数组是一个存放指针的数组。如:int* arr[3] --- 这就是一个存放整型指针的数组。

一种不推荐的写法:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[3] = { &a,&b,&c };
	int i = 0;
	for ( i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}

---

推荐的写法:

int main()
{
	int a[5] = { 1,2,3,4,5 };
	int b[] = { 2,3,4,5,6 };
	int c[] = { 3,4,5,6,7 };

	int* arr[3] = { a,b,c };

	return 0;
}

这时候,数组arr的三个元素都是int*类型的。

arr
int* →(a) 1  2  3  4  5
int* →(b) 2  3  4  5  6
int* →(c) 3  4  5  6  7

通过这种方式,可以访问每个数组的每一个元素。那么要如何访问呢?

int main()
{
	int a[5] = { 1,2,3,4,5 };
	int b[] = { 2,3,4,5,6 };
	int c[] = { 3,4,5,6,7 };

	int* arr[3] = { a,b,c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(arr[i] + j));
                        //printf("%d ", arr[i] [j]);
		}
	}
	return 0;
}

打印结果:1 2 3 4 5 2 3 4 5 6 3 4 5 6 7

补充:[printf("%d ", *(arr[i] + j))] 在这里有另一种写法(模拟二维数组)[printf("%d ", arr[i] [j])],结果完全一致。

数组指针

  • 整型指针 - 指向整型的指针 - int* p
  • 字符指针 - 指向字符的指针 - char* p

数组指针 - 指向数组的指针

int main()
{
	int arr[10] = { 1,2,3,4,5 };

	int(*parr)[10] = &arr;

	return 0;
}

此时的parr就是一个数组指针 - 其中存放的是数组的地址。

理解:[int (*) [10]]表示的是指针的类型,通过(*xxx)表示这是指针,[10]表示这是个数组指针。即数组名表示首元素地址在这里是表示的是第一行这个一维数组的地址

(ps:[]的优先级要高于*号,所以必须加上 ( ) 来保证p和*先结合。)

以此类推:

double* d[5];
double* (*pd)[5] = &d;

注意:指针要和指向数组的类型和大小相一致。

知识点

  • arr和&arr指向的地址一模一样。

笔记19-1(C语言进阶 指针进阶)_第4张图片

但是arr和&arr的类型不一样:

  • arr - 数组名是数组首元素的地址 - arr[0]的地址
  • ps:两个例外
  1. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组的大小(单位是字节)。
  2. &数组名 - 数组名表示整个数组,取出的是整个数组的地址(如下)。
  • &arr - 取出的是数组的地址
  • 如果要存取二者的地址,会发现:
    int* p1 = arr;
    int (*p2)[10] = &arr;
  •  其中p1是个整型指针,加1跳过1个字节;而p2是一个数组指针,加1跳过1个数组:

笔记19-1(C语言进阶 指针进阶)_第5张图片

p1 (前进4个字节)→ p1 + 1

p2 (前进40个字节)→ p2 + 2

结论:&arr 表示数组的地址,而不是数组首元素的地址。数组的地址+1,跳过整个数组的大小,所以 &arr + 1 相对于 &arr 的差值是40。

数组指针的使用

数组指针一般在二维数组内使用。如果在一维数组内使用:

譬如需要打印数组内每一个元素

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

	int(*pa)[10] = &arr;//数组指针
	int i = 0;
	for (size_t i = 0; i < 10; i++)
	{
		printf("%d ",*((*pa) + i));//*pa相当于arr
	}
	return 0;
}

这种写法别扭而且麻烦。

应用情景(打印二维数组的每一个元素)

注意:

        二维数组的数组名表示元素的地址。而二维数组的首元素是:第一行(相当于第一行是一个一维数组)。

传统写法

void print1(
	int arr[3][5], int r, int c
)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[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);
	return 0;
}

---

指针写法

print2(
	int(*p)[5], int r, int c
)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));//p + i 指向了某一行
		}
		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);
}

分析:

  1. p是一个数组指针,指向了二维数组的某一行 
  2. *(p + i) 就相当于某一行第一个元素的地址

笔记19-1(C语言进阶 指针进阶)_第6张图片

小结

int arr[5]; 整型数组
int* parr1[10]; 整型指针的数组
int(*parr2)[10]; 数组指针,该指针能够指向一个数组,这个数组有10个元素,每个元素的类型是 int。
int(*parr3[10])[5]; parr3是一个存放数组指针的数组,该数组能够存放10个数组指针。每个数组指针能够指向一个数组,这些数组是5个元素,每个元素是int类型。

数组参数、指数参数

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

一维数组传参

#include
void test(int arr[])//Ok?
{}
void test(int arr[10])//Ok?
{}
void test(int *arr)//Ok?
{}
void test2(int *arr[20])//Ok?
{}
void test(int **arr)//Ok?
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test(arr2);
}

解答(顺序从上到下):

  • [void test(int arr[])] 可行;
  • [void test(int arr[10])] 可行(不过[10]可以省略);
  • [void test(int *arr)] 可行,分析:传输的是整型数组的首元素的地址,用整型指针接收。

---

  • [void test2(int *arr[20])] 可行(不过[20]可以省略),分析:arr2是一个存放int*(整型指针)的数组,函数接收时也是使用整型指针的数组进行接收,故可行。
  • [void test(int **arr)] 可行,此时arr2存放int*:

函数使用二级指针接收,故可行。

二维数组传参

#include
void test(int arr[3][5])//Ok?
{}
void test(int arr[][])//Ok?
{}
void test(int arr[][5])//Ok?
{}

void test(int* arr)//Ok?
{}
void test(int* arr[5])//Ok?
{}
void test(int (*arr)[5])//Ok?
{}
void test(int **arr)//Ok?
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

ps:二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便计算。

解答(顺序从上到下):

  • [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])] 可行,分析:(*arr)表示一个指针,这个指针可以指向一个5个元素的一维数组;
  • [void test(int **arr)] 不可行,分析:(int **arr)是一个二级指针,但是传输过来的是第一行一维数组的地址,匹配不上。

一级指针传参

#include
void print(
	int* ptr, int sz
)
{
	int i = 0;
	for ( i = 0; i < sz; i++)
	{
		printf("%d ", *(ptr + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//p是一级指针
	int sz = sizeof(arr) / sizeof(arr[0]);

	print(p, sz);

	return 0;
}

这就是一种一级指针传参。

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

比如:

void test(
	char* p
)
{}

1.这样的一个函数,可以接收变量的地址

char ch = 'w';
test(&ch);

2.或者接收一级指针

char ch = 'w';
char* p1 = &ch;
test(p1);

二级指针传参

void test(
	int** p2
)
{
	**p2 = 20;
}
int main()
{
	int a = 10;
	int* pa = &a;//pa是一级指针
	int** ppa = &pa;//ppa是二级指针
	
	//把二级指针进行传参
	test(ppa);
	printf("%d\n", a);

	return 0;
}

这是一种二级指针。

思考:
|||   当函数的参数是二级指针时,函数能接收什么参数?

1.接收二级指针

test(ppa);

2.接收一级指针的地址

test(&pa);

3.接收存放一级指针的数组

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

总结

指针

一级指针

  • int* p; - 整形指针 - 指向整型的指针
  • char* pc; - 字符指针 - 指向字符的指针
  • void* pv; - 无类型的指针
  • ……

二级指针

  • char**p;
  • int**p;
  • ……

数组指针:指向数组的指针

  • int(*p)[4];
  • ……

数组

一维数组、二维数组

指针数组 - 存放指针的数组

你可能感兴趣的