C—指针

225 阅读11分钟

😎指针(12.16)

指针也有常量和变量之分

每一个字节表示8个二进制位。 而计算机为存储空间中的每一个字节都分配了一个编号,称为“编址”,这个编号就是我们常说的内存地址(简称地址),编址时保证内存中的每一个字节都有独一无二的地址号

变量以它所占的那块内存的第一个字节的地址(也叫起始地址)来表示该变量在内存中的地址

编译环境和操作系统都可能影响到显示的地址值,因为地址是系统自动分配的

🤓🤓指针变量

定义

是用于保存地址值的变量,例如int *p,其中int *表示指针类型

通过指针变量得到的内存内容,属于间接访问

初始化和赋值

  1. 总结
  2. 可以直接初始化一个变量的地址,也可以先定义然后再给它赋值一个变量的地址
  3. 可以重新赋值覆盖
  4. 同类型指针间可以相互赋值 QQ图片20231216192500.jpg

非法指针

使用指针变量的过程中需要始终关注该指针变量的指向,避免出现非法使用指针的情况

QQ图片20231216193917.jpg

计算

  1. 指针变量能进行自加、自减加减一个整数的运算,但是不同于普通变量的增减,指针变量的增减是以指针变量的基类型所占字节大小为单位的,也就是说每次增减1,地址值变化是1个基类型所占的存储空间的字节数
  2. 都是单目运算符,优先级相同,结合方向是自右向左
    • & 取地址符,操作数是变量,包括普通变量和指针变量,得到的是变量的地址
    • 星号* 间接引用运算符(也称指针运算符),操作数必须是地址值,大部分情况下是指针变量,得到该地址对应空间中存放的内容,所以对普通变量进行间接引用运算是错误的

😲😲指针与数组

C语言规定,用数组名表示数组第一个元素的地址,也就是这段连续空间的起始地址——数组首地址,因此数组名实质上是一个指针常量(地址常量)

  • 比如数组名score表示的是地址名,并且和&score[0]是一致的,也就是数组名代表该数组的首地址
  • 根据上面指针可以加减整数可知,score+1也就是&score[1]

指针访问一维数组

因为数组名是指针常量,通过数组名可以访问连续存储的元素,但是该指针常量不能改变

为了更加灵活地访问数组元素,我们还可以定义一个指针变量来指向数组,并通过移动指针来访问各个数组元素(原来是通过移动下标的方法)

指针与二维数组

  1. 二维数组的存储遵循 “行优先” 的原则。 - (如:定义int a[3][2],存储顺序为,先存储第一行的a[0][0],a[0][1],然后是第二行....
  2. 因此,二维数组名表示的是一个行地址。每次移动,移动一行,所以a、a+1、a+2分别指向二维数组的第0行、第1行和第2行
  3. a[i]+[j],表示一个列地址,每次移动,移动一列

QQ图片20231216202538.jpg

👻👻👻一级指针变量访问二维数组元素

int* p = &a[0][0]

#include<stdio.h>
int main() {
	int a[3][2] = {1,2,3,4,5,6};
	int i;
	int* p = &a[0][0];
	for (i = 0; i < 6; i++) {
		printf("%p\t%d\n", p + i, *(p + i));
	}
	return 0;
}

image.png

👻👻👻用行指针变量访问二维数组元素

二维数组中有两个不同类型的地址值:行地址和列地址

示例:int (*p)[2],其中定义的行指针变量p,其基类型为int[2]

#include<stdio.h>
int main() {
	int a[3][2] = {1,2,3,4,5,6};
	int i,j;
	int(*p)[2];//p定义成了行指针

	p = a;
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 2; j++) {
			printf("%p\t%d\n", p[i] + j, p[i][j]);
		}
	}
	return 0;
}

结果跟上一个一样

🥳🥳指针数组(本身是个数组)

就是数组元素为一级指针变量的数组

例子:int *p[3]

即定义了一个长度为3的指针数组,数组中的元素都是基类型为int的指针变量,数组名p表示该指针数组的起始地址。指针数组的元素是一级指针变量

👻👻👻用指针数组来访问二维数组元素

int *p[3]

#include<stdio.h>
int main() {
	int a[3][2] = {1,2,3,4,5,6};
	int i,j;
	int* p[3]; //定义一个长度为3的指针数组

	for (i = 0; i < 3; i++) {
		p[i] = a[i]; //为指针数组中的每一个元素赋值
		for (j = 0; j < 2; j++) {
			printf("%p\t%d\n", p[i] + j, p[i][j]);
		}
	}
	return 0;
}

结果跟之前一样

🧐🧐 指针与函数

传值和传地址

如果实参给形参传递的是地址值,则称为地址调用,简称为传地址;否则称为值调用,简称传值

  • 传值调用要求形参为普通数据类型

  • 而传地址调用要求形参为指针变量(数组形式的形参实质上就是指针形参变量),因为只有指针变量才能接受地址值,并且对应的实参必须是地址值

🤖🤖🤖传值和地址的例子(实现两数的交换)

1.传值

#include<stdio.h>
void ReserveValue(int,int);
int main() {
	int a = 3;
	int b = 4;
	ReserveValue(a,b);
	printf("a=%d\tb=%d\n",a, b);
	return 0;
}
void ReserveValue(int x,int y){
	int temp;
	temp = x;
	x = y;
	y = temp;
	printf("x=%d\ty=%d\n", x, y);
}

结果是:

x=4 y=3
a=3 b=4

详情见下面的补充

2.传地址

#include<stdio.h>
void ReserveValue(int*,int*);
int main() {
	int a = 3;
	int b = 4;
	ReserveValue(&a,&b);//两个地址实参
	printf("a=%d\tb=%d\n",a, b);
	return 0;
}
void ReserveValue(int *px,int *py){
	//px,py是两个指针变量,作为形参
	int temp;
	temp = *px;
	*px = *py;
	*py = temp;
	printf("*px=%d\t*py=%d\n", *px, *py);
}

结果是:

*px=4 *py=3
a=4 b=3

分析总结: 传地址调用通过把主调函数中变量地址作为实参传递给被调函数的指针形式的形参,并在被调函数执行时通过对指针形参的间接引用来访问主调函数的变量。本质上,通过这种方式是把主调函数中变量的作用域扩展到被调函数中。

👆补充:关于形参空间释放问题

传值

我们从上面的结果可以看出,a和b的值并没有交换。因为main函数中的实参a、b和下面ReserveValue函数的形参x和y占用的是两组不同的空间,具有不同的作用域。

所以在参数传递的时候,实参a和b的值会分别复制到形参x和y中,ReserveValue函数执行时,交换了形参x和y的值,而当ReserveValue函数调用结束的时候,形参x和y的空间释放,而整个过程中实参a和b的值没有交换。

image.png

👆👆思考题(上面例子的扩展):

问:如果上面的代码改成下面这样还可以使a、b交换吗?

不能

void ReserveValue(int *px,int *py){
	int *temp;
	temp = px;
	px = py;
	py = temp;
	printf("*px=%d\t*py=%d\n", *px, *py);
}
//结果
//*px=4   *py=3
//a=3     b=4

为什么?

函数ReserveValue的目的是交换两个整数的值。然而,你却试图交换两个指针,而不是指针所指向的值。

image.png

指针作形参返回多个值(返回的是值)

问题来源: 每次函数调用最多只通过return语句显式地返回一个结果,而在很多问题中可能需要返回多个结果,这就无法完全依靠return语句来实现了。

解决方案: 上面传地址那里可以利用指针形参的间接引用来改变对应实参变量的值,从而达到多个计算结果返回主调函数的目的。(结果通过指针形参隐式地返回给主函数)

操作:

  1. 1.设定指针形参
  2. 2.在主调函数中传入相应变量的地址

这样主调函数的那些传地址的变量就可以获得对应指针形参通过间接引用方式改变后的值

例子:

#include<stdio.h>
void calculate(int x, int y, int* su, int* di) {
	*su = x + y;
	*di = x - y;
}
int main() {
	int a = 3, b = 4;
	int sum, diff;
	calculate(a, b, &sum, &diff);
	printf("%d与%d的和是:%d\n", a, b, sum);
	printf("%d与%d的差是:%d\n", a, b,diff);
	return 0;
}

👆👆思考题补充:

kkkkkkkkkkkkkk.jpg

hhhhhhhhhhhhhh.jpg

返回指针的函数(返回的是指针)

语法示例:int* smaller(int* x, int* y);

#include<stdio.h>
//定义的是一个返回类型为 int*(也就是地址)的函数
int* smaller(int* x, int* y);
int main() {
	int a, b;
	int* s;//定义一个指针变量
	printf("请输入两个整数:");
	scanf_s("%d%d", &a, &b);
	s = smaller(&a, &b);
	printf("最小的数是:%d\n", *s);
	return 0;
}
int* smaller(int* x, int* y) {
	if (*y < *x) {
		return y;
	}
	return x;//返回的是地址
}

函数返回的地址值可以给主调函数中同类型的指针变量赋值

🫤🫤应用举例:p140

😽😽😽批量数据的筛查

😽😽😽进制转换

😽😽😽选择法排序

😽😽😽矩阵运算

aa.jpg

bb.jpg

cc.jpg

dd.jpg

ee.jpg

🤯🤯指针进阶

const与指针的结合

const和指针结合(根据const的位置)可以得到不同的常量,如:常指针指向常量的指针指向常量的长指针

1.jpg

二级指针(指针的指针)

指针变量作为一种变量,也有自己的值和地址

所以如果需要一个变量来保存指针变量p的地址,该怎么办呢?

这时我们可以定义一个二级指针变量存放指针变量的地址,即指向指针的指针

定义形式为:

//类型标识符 **二级指针变量名
int **c;

类型标识符 确定了二级指针指向的指针变量的基类型(类型标识符*)

指针与动态空间

11.jpg

2.jpg

3.jpg

4.jpg

5.jpg

指向函数的指针(函数指针)

C语言中,一个函数大代码总是占用一段连续的内存区,而函数名就是该函数代码所占内存区的首地址(入口地址)

而把这个函数的函数首地址赋值给一个指针变量,使得该指针变量指向这个函数,然后我们就可以通过这个指针变量找到并调用这个函数。

所以总的来说,这种指向函数的 指针变量称为函数指针。 7.jpg

6.jpg

😮TIP:原码,补码,反码

a.jpg


🤡👇听网课写的笔记,一团糟没条理,看着看着就放弃了,所以还是自己扣书+看博客琢磨吧

地址

%p 取地址

对于数组来说,直接把变量放进去,即使不加&与号,它也表示地址

image.png

指针

不能用普通的int、长短整型去接收地址

所以我们这里引入一个新的数据类型,指针类型

image.png 在定义指针变量的时候,*是用来修饰变量的,说明变量是个指针变量

int占四个字节,取的时候取的是最小的那个

#include<stdio.h>
int main() {
	int a = 100;
	int *p = &a;
	printf("%d\n", a);
	printf("%p\n", &a);
	printf("%p\n", p);
}

如何通过地址访问变量?

*p=1000//可以直接修改你在函数中传的那个变量值

用指针写冒泡排序

#include<stdio.h>
void swap(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}
void sort(int *myarr,int length) {
	for (int i = 0; i < length- 1; i++) {
		for (int j = 0; j < length- 1 - i; j++) {
			if (myarr[j] >myarr[j + 1]) {
				swap(&myarr[j], &myarr[j + 1]);
			}
		}
	}
}


int main() {
	int arr[] = { 44,11,33,22,5,6,0,66 };
	//调用排序函数
	sort(arr, sizeof(arr) / sizeof(int));

	for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
		printf("%d\n", arr[i]);
	}
}

指针与数组

image.png &arr +1可以直接找到下个地址,它会跳过之前整个数组的长度

指针的分类

什么类型,就只能存什么型的地址

  • char *p 字符指针(char只能修改一个字节)
  • short *p 短整型指针
  • int *p 整型指针
  • long *p 长整型指针
  • float *p
  • double *p
  • 函数指针
  • 结构体指针
  • 指针的指针
  • 数组指针
  • 通用void*p指针
  • 无论什么类型的指针变量,在32位系统下,都是4个字节,在64位都是8个字节
  • 指针只能存放对应类型的变量的地址编号

指针与字符串

image.png 特例:*p后可以写字符串,但不能写{k,e,r(单引号)}这样的

不过只读不能写,解锁了文字常量

指针数组

定义一个数组,数组中有若干个相同类型的指针变量,这个数组被称为指针数组int *p[5]

指针数组本身是个数组,是个指针数组,是若干个相同类型的指针变量构成的集合

image.png 可以用一个指针数组取到所有字符串

image.png

指针的指针

一级指针只能访问一级

而想要二级指针,需要写两个星号

image.png

数组指针

本身是个指针,指向一个数组,加1跳一个数组,即指向下一个数组

  • 不过至少指向二维数组才有意义

数组指针和指针数组的区别

int *p[3]=arr    //指针数组
int (*p)[3]=arr  //数组指针