C语言教程---指针

1什么是指针

1.1概念

指针(Pointer)是一个变量,其值为另一个变量的地址。通过指针,我们可以间接地访问和操作其他变量的数据。指针本质上存储的是内存地址,而不是数据本身。

1.2指针的声明

指针的声明使用 * 符号来表示,表示指针指向某种类型的数据。例如:

int *ptr; // ptr是一个指向int类型的指针,ptr并不存储值,而是存储一个整数变量的地址。

1.3指针的初始化

指针在使用前通常需要初始化,即给它赋一个有效的内存地址。可以使用地址运算符 & 获取变量的地址:

int num = 10;
int *ptr = # // ptr指向num的地址

1.3解引用操作

解引用操作通过 * 符号进行,用于获取指针指向的变量的值。即通过指针访问或修改存储在该地址上的数据。例如:

int num = 10;
int *ptr = #
printf("%d\n", *ptr); // 输出 10,*ptr表示ptr指向的变量的值

下面来实现个完整的例子来理解上面的内容吧

#include<stdio.h>

int main() {

	int num = 10;

	// 指针的声明和初始化,ptr指针指向num的地址,而不是值
	int* ptr = &num;
	printf("ptr = %p\n",ptr);
	// 解引用操作
	printf("ptr指针指向的值是:%d\n",*ptr);

	return 0;

}

2.内存地址

内存的组成

内存由许多存储单元组成,每个存储单元可以存储一个字节。一个字节由8位组成,每个位可以是0或1。

内存地址

内存可以抽象成一个很大的一维字节数组。内存管理系统为每个字节分配一个唯一的编号,这个编号称为内存地址。内存地址的范围与处理器的位宽(32位或64位)有关。位宽决定了处理器一次能处理多少位数据,同时也决定了处理器能生成多少个不同的内存地址。

举个例子

假设内存是一个大仓库,仓库里有很多小格子(每个格子是一个字节),每个格子都有一个唯一的编号(内存地址)。

  • 32位处理器:仓库的编号范围是 0 到 2^32−1(即 0 到 4,294,967,295),所以仓库最多只能有 4GB 的格子。

  • 64位处理器:仓库的编号范围是 0 到 2^64−1(即 0 到 18,446,744,073,709,551,615),所以仓库可以有 16EB 的格子。

内存中的每一个数据都会分配相应的地址:

  • char:占一个字节分配一个字节地址

  • int: 占四个字节分配连续的4个字节地址

  • float、struct、函数、数组等

3.指针变量

3.1指针和指针变量

内存区的每一个字节都有一个编号,这就是“地址”。

如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号),指针的实质就是内存“地址”。指针就是地址,地址就是指针。

指针存储的是一个内存地址,指针变量是一个存放内存地址的变量。

通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。

指针也是一种数据类型,指针变量也是一种变量 例如:int *x;表示的是指针变量x是int *类型的 类比:int x;表示的是变量x是int类型的

指针变量指向谁,就把谁的地址赋值给指针变量

“*”操作符操作的是指针变量指向的内存空间

“&”操作符是取地址符

#include <stdio.h>

int main() {

		int a = 0;
		char b = 100;

		printf("a = %p b = %p\n", &a, &b);// &是取地址符,取a和b的地址的值

		// 定义一个指向int类型空间的指针变量
		// &a:获取a变量在内存中的首地址,&不能对寄存器变量(因为寄存器变量在CPU里)取地址
		int* p = &a;
		printf("p = %p\n", p);

		// *p访问p指向的地址空间中具体的内容
		printf("*p = %d\n", *p);

		char* p1 = &b;
		printf("p1 = %p\n", p1);
		printf("*p = %d\n", *p1);

		

		return 0;
	}

3.2通过指针间接修改变量的值

#include<stdio.h>

int main() {

	int a = 10;
	int b = 20;

	printf("a = %d,b = %d\n", a, b);

	int* p = &a;// 指针变量p指向a的地址

	// 使用指针间接修改a的值
	*p = 100;//*p就是a

	printf("a = %d,*p = %d\n", a, *p);

	// p指向b的地址
	p = &b;
	*p = 200;
	printf("b = %d,*p = %d\n", b, *p);


	return 0;
}

3.3指针大小

  • 使用sizeof()测量指针的大小,得到的总是:4或8,因为指针是地址

  • sizeof()测的是指针变量指向存储地址的大小

  • 在32位平台,所有的指针(地址)都是32位(4字节)

  • 在64位平台,所有的指针(地址)都是64位(8字节)

下面在不同的平台都测试一下:

64位平台

#include<stdio.h>

int main() { 
		
	int* p1;
	int** p2;//指向指针的指针
	char* p3;
	short** p4;
	printf("sizeof(p1) = %d\n", sizeof(p1));
	printf("sizeof(p2) = %d\n", sizeof(p2));
	printf("sizeof(p3) = %d\n", sizeof(p3));
	printf("sizeof(p4) = %d\n", sizeof(p4));
	printf("sizeof(double *) = %d\n", sizeof(double*));

	return 0;
}

32位平台

#include<stdio.h>

int main() { 
		
	int* p1;
	int** p2;//指向指针的指针
	char* p3;
	short** p4;
	printf("sizeof(p1) = %d\n", sizeof(p1));
	printf("sizeof(p2) = %d\n", sizeof(p2));
	printf("sizeof(p3) = %d\n", sizeof(p3));
	printf("sizeof(p4) = %d\n", sizeof(p4));
	printf("sizeof(double *) = %d\n", sizeof(double*));

	return 0;
}

4.特殊指针

4.1野指针和空指针

野指针:野指针指的是指针中保存的是无效的内存地址,如果用户直接使用的话系统会提示段错误(Segmentation fault(core dumped),一般由用户访问了非法的内存导致的)

指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义(因为这个数值可能不是一个有效的内存),这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,虽然野指针本身不会直接引发错误,但是操作野指针指向的内存区域就会出问题。

int a = 100;
int *p;

p = a; // 把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义
p = 0x12345678; // 给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
*p = 1000;  // 操作野指针指向未知区域,内存出问题,err

下图来解释一下:

空指针:标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。

int *p = NULL;

其中这个NULL是一个值为0的宏常量:

#define NULL  ((void *)0)

4.2万能指针

void *是一种特殊的指针类型,可用于存放任意对象的地址,例如

int a = 10;
void *p = &a;

缺点:由于不知道地址中存放的是何种数据类型,因此不能直接操作void*指针所指的对象

#include<stdio.h>

int main() {

	void* p = NULL;// 万能指针,可以指向任意类型的变量地址

	int a = 10;

	int* p1 = &a;

	double d = 12.345;
	double* p2 = &d;
	
	//warning C4133: “=”: 从“int *”到“double *”的类型不兼容
	//尝试在a的地址开始的8个字节内进行修改,但是a是4个字节,要是修改8个,会出现越界错误,运行后发现错误
	p2 = &a;  

	p = &a;//指向int类型地址的指针
	p = &d;//指向double类型的地址

	//*p = 12.3456;//报错:"="无法从double类型转换为void,因为*p是void类型,不能赋值。编译报错

	//解决
	//(double*)p将void指针类型转换成double指针类型,再加个*表示取地址的内容
	*((double*)p) = 12.345678;


	printf("a = %d,d = %lf\n", a, d);

	return 0;
}

5.一级指针和一维数组

在前面的数组笔记中介绍了一个知识点就是:数组名字是数组的首元素地址,但他是一个常量,不能更改

来验证一下

#include<stdio.h>

int main() {

	int a[] = { 1,2,3,4,5 };
	//a = 10; //err, 数组名只是常量,不能修改
	printf("a = %p\n", a);// 数组名
	printf("&a[0] = %p\n",&a[0]);// 数组的首元素

	return 0;
}

5.1指针操作数组元素

在操作数组元素之前先来看个图解来方便理解

来个例子演示一下指针操作数组元素

#include<stdio.h>

int main() {

	int a[] = { 1,2,3,4,5 };
	//a = 10; //err, 数组名只是常量,不能修改
	printf("a = %p\n", a);// 数组名
	printf("&a[0] = %p\n",&a[0]);// 数组的首元素

	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);
	// 遍历输出数组的值
	for (i = 0;i < n;i++) {
		//printf("%d ",a[i]);
		printf("%d ", *(a + i));
	}
	printf("\n");

	// 通过指针来操作数组的元素
	int* p = a; //定义一个指针变量保存a的地址
	for (i = 0;i < n;i++) {
		p[i] = 2 * i;
	}
	for (i = 0;i < n;i++) {
		//printf("%d ",*(p + i));
		printf("%d ",p[i]);
	}
	printf("\n");

	return 0;
}

5.2指针的运算

指针计算不是简单的整数相加或相减,而是依赖于指针所指向的数据类型的大小。

1.指针加法运算

ptr = ptr + n;

指针 ptr指向某个数据类型的变量。n 是一个整数,它将指针的地址偏移 n 个数据单元的大小。这里的“数据单元”是指 ptr所指向类型的数据大小。

假设 ptr是一个 int* 类型的指针,且 int 类型的大小是 4 字节,那么

ptr + 1  // 会将指针向前偏移 4 字节(一个 int 大小)
ptr + 2  // 会将指针向前偏移 8 字节(两个 int 大小)

#include<stdio.h>

int main() {

	int a = 10;
	int* p = &a;

	printf("指针p存的内存地址为:%p\n",p);
	p += 2;// 相当于加了两个int类型的空间大小
	printf("指针p存的内存地址增加了两个int类型的空间大小之后地址为:%p\n",p);

	return 0;

}

通过改变指针指向操作数组元素:

#include<stdio.h>

int main() {

	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);

	int* p = a;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", *p);
		p++;
	}
	printf("\n");

	return 0;

}

2.指针减法运算

#include<stdio.h>

int main() {

	char c = 'A';
	char* q = &c;
	printf("指针q存的内存地址为:%p\n", q);
	q -= 2;// 相当于减了两个char类型的空间大小,即2字节
	printf("指针q存的内存地址减少了两个char类型的空间大小之后地址为:%p\n", q);

	return 0;

}

通过改变指针指向操作数组元素:

#include<stdio.h>

int main() {

	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);

	int* p = a + n - 1; // 指针p指向数组最后一个元素的地址,从后往前遍历
	for (i = 0; i < n; i++)
	{
		printf("%d, ", *p);
		p--;
	}
	printf("\n");

	return 0;

}

3.指针减指针

#include<stdio.h>

int main() {

	//指针 - 指针
	//当两个指针都指向的是同一个数组中的元素的时候可以用,表示两个指针在内存中的距离
	int a[] = { 1,2,3,4,5,6,7,8,9 };
    
	int* p3 = &a[2];// 数组中第三个元素的地址
	int* p0 = &a[0];// 数组中第1个元素的地址
    
	printf("p3 = %p,p0 = %p\n",p3,p0);
    
	int d1 = p3 - p0;//两个指针相减,得到两个地址间元素的个数
	int d2 = (int)p3 - (int)p0;//两个地址的值相减
    
	printf("d1 = %d\n", d1);
	printf("d2 = %d\n",d2);


	return 0;

}

6.多级指针

多级指针(也称为指针的指针)是指指向指针的指针。在C语言中,指针本身就是一个变量,它保存的是另一个变量的内存地址。多级指针则是指一个指针指向另一个指针,而这个第二个指针又指向一个实际的数据。

本质:

所有的指针都是用来保存地址的,只不过因为保存地址的数据类型不同,从而拥有多种指针类型。

规则:

一级指针变量是用来保存普通变量的地址的

        二级指针变量是用来保存一级指针变量本身自己的地址

                三级指针变量是用来保存二级指针变量本身自己的地址

                        .........

6.1二级和三级指针

二级指针是:指向指针的指针,可以的定义如下

int a = 10;
int *p = &a;// 一级指针
int **q = &p;// 二级指针,保存的是一级指针变量本身的地址,即一级指针p的地址的

其中p存储的是a的地址,q存储的是p的地址

然后如何访问数据呢?

  • *p解引用一级指针p,得到对应a地址上的值

  • *q解引用二级指针q,得到的是p(即指向a的指针),然后通过*p再解引用,得到a的值

图解

规则:* + 地址表示访问地址中的内容

来看个例子

#include<stdio.h>

int main() {

	int a = 10;
	int* p = &a;//一级指针

	printf("p = %p\n", p);
	printf("*p = %d\n", *p);//*p等价于a

	//二级指针
	int** q = &p;//表示q是一个int *的地址,因为p是int *类型的
	printf("q = %p\n", q);
	printf("*q = %p\n", *q);//*q等价于p
	printf("**q = %d\n", **q);//**q等价于*p,也等价于a

	//三级指针
	int*** t = &q;
	printf("t = %p\n", t);
	printf("*t = %p\n", *t);//*t等价于q
	printf("**t = %p\n", **t);//**t等价于*q 等价于p
	printf("***t = %d\n", ***t);//***t等价于**q 等价于*p 等价于a


	return 0;
}

6.2指针的使用结论

1.再32位的操作系统中,所有的指针变量都是4个字节

再64位的操作系统中,所有的指针变量都是8个字节

下面我只演示一下64位操作系统的

#include<stdio.h>

int main() {

	int* p = NULL;
	char** p_char = (char**)&p;
	char** p_short = (short**)&p;
	char** p_int = (int**)&p;

	printf("sizeof(p_char) = %d\n", sizeof(p_char));
	printf("sizeof(p_short) = %d\n", sizeof(p_short));
	printf("sizeof(p_int) = %d\n", sizeof(p_int));
	printf("sizeof(int *) = %d\n",sizeof(int *));

	return 0;
}

2.在32位的操作系统中,多级指针(二级和二级以上)在移动的时候,每次移动都是4个字节,因为一个指针大小4个字节

在64位的操作系统中,多级指针(二级和二级以上)在移动的时候,每次移动都是8个字节,因为一个指针大小8个字节

下面我只演示一下32位操作系统的吧

#include<stdio.h>

int main() {

	// 这里演示指针的移动
	int a = 10;
	char c = 'A';
	int* p = &a;
	char* q = &c;

	printf("int类型的一级指针p为:%p\n", p);
	printf("char类型的一级指针q为:%p\n",q);

	p++;
	q++;

	printf("int类型的一级指针p移动一次的时候,p为:%p\n", p);
	printf("char类型的一级指针q移动一次的时候,q为:%p\n", q);

	int** pp = &p;
	char** qq = &q;

	printf("int类型的二级指针pp为:%p\n", pp);
	printf("char类型的二级指针qq为:%p\n",qq);

	pp++;
	qq++;

	printf("int类型的一级指针pp移动一次的时候,pp为:%p\n", pp);
	printf("char类型的一级指针qq移动一次的时候,qq为:%p\n", qq);


	return 0;
}

6.3二级指针和一维数组的转换

在前面的介绍中知道一维数组的本质是一个指针,假设有如下的一个数组

int a[3] = {1,2,3};

这个数组a是一个指向数组首元素a[0]的指针,实际上,a就是&a[0],即数组首元素的地址。

因此a和&a[0]是等价的,都是指向a[0]的指针。

首先定义一个指针p1,指向数组a的首元素

int *p1 = a;//p1指向a[0]

在这里,p1 是一个指向 int 类型的指针,而 a 是一个 int 数组,它的类型是 int[3],因此 p1保存的是 a[0]的地址。

然后定义一个二级指针p2指向p1

int **p2 = &p1;// p2指向p1

在这里,p2 是一个指向一级指针的指针,它保存的是 p1 的地址。

通过 *p2 解引用二级指针p2,可以得到 一级指针p1,即

*p2 == p1;//访问到 p1, 也就是指向 a[0] 的指针

因此,*p2是指向数组 a 的指针,具体来说,它指向 a[0]。

然后,*p1解引用一级指针p1,访问数组中的第一个元素a[0]。即

*p1 == a[0]; // 访问 a[0],也就是数组的第一个元素

既然*p2是指向数组a的指针,所以*p2 + 0是指向数组第一个元素的地址 &a[0],是一个地址,类似的*p2 + 1是数组第二个元素的地址,以此类推,然后如果现在我们想操作这个数组的值,通过解引用地址来获取值,即*(*p2 + 0)、*(*p2 + 1)等,同时也等价于(*p2)[0]、(*p2)[1]等

总结:

*(*p2 + i)和(*p2)[i]都可以访问数组的第i个元素,两者等价的

下面来验证一下是否正确

#include<stdio.h>

int main() {

	int a[3] = { 1,2,3 };
	int* p = a;
	int** q = &p;// q = &p; *q <===>*(&p) <===> p

	printf("二级指针遍历一维数组输出:\n");
	for (int i = 0;i < 3;i++) {
		//printf("*(*q + %d) = %d\n",i,*(*q + i));
		printf("(*q)[%d] = %d\n",i,(*q)[i]);
	}

	return 0;
}

7.指针数组

指针数组是的本质还是一个数组,只不过里面的数据存放的是指针类型的,即数组中的元素是指向某个类型的指针,也就是说,指针数组是存储指针的数组,数组中的每个元素都是一个指向变量或数据结构的指针。定义一个指针数组相当于定义了多个指针变量。

7.1定义

语法如下:

类型 *数组名[数组大小];

其中:

  • 类型:指针所指向的数据类型

  • *:表示元素是一个指针

  • 数组名:数组的名称,要符合命名规则

  • 数组大小:数组中元素的数量

7.2使用

下面定义了一个指针数组,数组中的每个元素都指向了一个int类型的变量,有如下定义

int *arr[5];
    arr 是一个包含 5 个元素的数组;
    arr 的每个元素是一个指向 int 类型的指针。    
      数组中的元素:arr[0],arr[1],arr[2],arr[3],arr[4]
    数组中每个元素的类型:int *
    整个数组的大小:sizeof(arr)   ===>   元素个数  *  指针大小(32位位4字节,64位位8字节)
    一个元素的大小:sizeof(arr)   ===>   sizeof(arr[0])字节
    元素的个数:sizeof(arr) / sizeof(arr[0])
    数组的首地址:arr   <===> &arr[0]

图解

代码验证

#include<stdio.h>

int main() {

	int a = 10, b = 30, c = 30;

	int* arr[3] = {&a,&b};
	arr[2] = &c;
	
	int len = sizeof(arr) / sizeof(arr[0]);

	printf("数组大小为:%d字节\n",sizeof(arr));
	printf("数组的长度为:%d\n",len);
	printf("变量a的地址为:%p\n", &a);
	printf("变量b的地址为:%p\n", &b);
	printf("变量c的地址为:%p\n",&c);

	printf("指针数组中的元素的地址值为:\n");

	for (int i = 0;i < len;i++) {
		printf("arr[%d] = %p\n",i,arr[i]);
	}
	printf("指针数组中指针指向的内容分别为:\n");
	for (int i = 0;i < len;i++) {
		//printf("*(*(arr + %d)) = %d\n",i,*(*(arr + i)));
		printf("*arr[%d] = %d\n",i,*arr[i]);
	}


	return 0;
}

7.3指针数组首地址的类型

假设有个如下的指针数组:

int *arr[3];

指针数组arr的首地址是数组的第一个元素的地址,也就是&arr[0],或者可以直接使用数组名arr,也代表了数组的首地址。

如果想要保存指针数组的首地址,就需要一个指向指针数组的指针。而指针数组arr中的每个元素都是一个int *类型的指针,因此指向指针数组的指针应该就是一个int **二级指针类型的。

例如,通过二级指针将指针数组中的元素输出:

#include<stdio.h>

int main() {

	int a = 100, b = 200, c = 300;
	int* t[] = {&a,&b,&c};

	int** q = t;//二级指针指向指针数组的首地址

	for (int i = 0;i < sizeof(t) / sizeof(t[0]);i++) {
		//printf("%d ",*t[i]);
		//printf("%d ", *(*(t + i)));
		//printf("%d ",*(*(q + i)));
		printf("%d ",*q[i]);
	}

	return 0;

}

8.数组指针

8.1定义

数组指针是指向数组的 指针变量 它存储的是数组的首元素的地址,可以用来操作数组或数组中的元素。

定义如下:

数据类型 (*变量名)[元素个数];
如:
    int (*p)[5];  //p是一个指向包含5个整数的数组的指针
    其中:
        int表示数组元素的类型
        (*p) 表示p是一个指针
        [5] 表示p指向的是一个包含5个元素的数组

8.2初始化

数组指针可以指向一个数组,如

int arr[5] = {1,2,3,4,5};
int (*p)[5] = &arr;//p指向数组arr
其中:
    &arr表示数组arr的地址,它的类型是 int (*)[5]的
    p 现在指向数组 arr

8.3数组指针和二级指针

回顾一下以前介绍的东西,

现在有一个数组,int a[3];数组a的类型是int [3]类型的

&a表示获取数组类型的地址,此时这个地址类型是int (*)[3]类型的,因为地址是使用指针来保存的嘛

现在有如下的关系:

a是一维数组,可以使用int *类型来保存,a+1表示移动1个元素的位置,4字节

&a是取一维数组的地址,可以使用int (*)[3]来保存,&a+1表示移动3个元素的位置,12字节

代码示例

#include<stdio.h>

int main() {

	int a[3] = { 1,2,3 };
	int* p = a;
	int (*p1)[3] = &a;

	printf("p = %p\n", p);
	printf("p + 1 = %p\n", p + 1);
	printf("p1 = %p\n", p1);
	printf("p1 + 1 = %p\n",p1 + 1);

	return 0;
}

现在假设有个二维数组,int b[3][2];

二维数组b表示的地址中存放了三个一维数组类型的数据,此时这个地址的类型是int (*)[2]

为什么类型是int (*)[2]类型的呢?

首先我们知道这个二维数组中存放了三个一维数组类型的数据,且这三个一维数组分别是b[0]、b[1]和b[2]

然后每个一维数组有两个元素,每个元素都是int类型的,内存中的布局大概是这样:

b[0][0]  b[0][1]
b[1][0]  b[1][1]
b[2][0]  b[2][1]

在内存中,数组b依次存储了所有的int元素

b[0][0], b[0][1], b[1][0], b[1][1], b[2][0], b[2][1]

因为指针指向的是数组首元素的地址,如果我们想要指向b数组的第一行b[0]的地址,因为b[0]的类型是int[2]类型的,即他是一个包含2个int元素的数组,因此,我们使用 int (*)[2]来表示指向包含 2 个整数的数组的指针。

#include<stdio.h>

int main() {

	// 定义一个3*2的二维数组
	int b[3][2] = {
		{1,2},
		{3,4},
		{5,6}
	};
	// 定义一个指向包含2个整数的数组的指针
	int (*p)[2] = b;

	// 打印出二维数组b和指针p的内容
	printf("b = %p\n", b);   // b实际上是指向b[0]的指针
	printf("p = %p\n", p);   // p指向b[0]
	printf("p[0][0] = %d\n", p[0][0]);  // p指向b[0],因此 p[0][0] = b[0][0]
	printf("p[1][0] = %d\n", p[1][0]);  // p++后指向b[1],p[1][0] = b[1][0]

	// 增加p,指向下一行
	p++;
	printf("p = %p\n", p);   // p指向 b[1]
	printf("p[0][0] = %d\n", p[0][0]);  // p[0][0] = b[1][0]

	return 0;
}

8.4数组指针的访问

通过数组指针来访问一维数组中的元素,例如:

#include<stdio.h>

int main() {

	int arr[5] = { 1,2,3,4,5 };
	int (*p)[5] = &arr;

	// 访问数组元素
	printf("%d\n", (*p)[0]); // 1
	printf("%d\n",(*p)[2]); // 3


	return 0;
}

其中:

  • (*p)用来解引用数组指针,得到数组本身

  • (*p)[i]访问数组的第i个元素

数组指针也可以用于操作二维数组,例如:

#include<stdio.h>

int main() {

	int arr1[3][4] = {
	{1, 2, 3, 4},
	{5, 6, 7, 8},
	{9, 10, 11, 12}
	};

	int (*p1)[4] = arr1;  // p1指向二维数组的第一行
	//p是一个指向包含4个整数的数组的指针
	//p + 1会指向二维数组的下一行
	// *(p + i)表示第i行的数组
	// *(*(p + i) + j)表示的是第i行第j列的元素
	for (int i = 0;i < 3;i++) {
		for (int j = 0;j < 4;j++) {
			printf("%d ",*(*(p1 + i) + j));
		}
		printf("\n");
	}

	return 0;
}

8.5数组指针与函数参数

数组指针可以作为函数参数传递,特别是在处理二维数组时非常有用。例如:

#include<stdio.h>

// 这个是函数,下一章会介绍到
void printArray(int (*p)[4],int rows) {
	for (int i = 0;i < rows;i++) {
		for (int j = 0;j < 4;j++) {
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}

int main() {

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


	return 0;
}

9关键字的使用

9.1含义

const关键字用来声明 常量 ,确保某个变量或指针的值在程序中不能被修改。可以与变量,指针和函数返回值等一起使用。

9.2基本用法

1.修饰普通变量

格式如下

数据类型 const 变量名 = 值;
或者
const 数据类型 变量名 = 值;

功能:const修饰给修饰的变量添加了只读的属性,无法通过变量名来直接修改变量的值。

例如

#include<stdio.h>

int main() {

	// 修饰普通变量
	const float pi = 3.14;
	// 或者
	//float const pi = 3.14;
	
	//pi = 100;//错误:表达式必须是可修改的左值,不能修改

	// 那有什么办法来修改const修饰的值吗
	// 有的有的
	// 地址可读可写,可以通过指针来间接的修改
	float* p = &pi;
	*p = 100;

	printf("pi = %.2f\n",pi);

	return 0;
}

2.const助记方法

这个方法我忘记是谁说的了,感觉挺有用的。

使用助记规则:可以使用“以星号为界左物右指”的规则来帮助记忆。

这里的“物”指的是指针指向的对象“指”指的是指针本身

这条规则意味着const在星号左边时,修饰的是指针所指向的对象;

在星号右边或没有星号时,修饰的是指针本身。

3.修饰指针变量

const修饰指针所指向的数据(常量数据)

#include<stdio.h>

int main() {

	// 修饰指针变量
	// 常量数据
	int a = 10;
	// 通过左物右指,const在*的左边,说明是物,说明不能通过p修改a的值
	const int* p = &a;
	//*p = 100;//报错:表达式必须是可修改的左值
	printf("%d\n",a);//还是10
	

	return 0;
}

const 修饰指针本身(常量指针)

#include<stdio.h>

int main() {
	
	// 修饰指针变量
	// 常量指针
	int a = 10;
	int b = 20;
	// 左物右指,const在*的右边,表示不能修改p指向的地址,但可以通过p修改*p的值
	int* const p = &a;
	*p = 30;//可以通过,p指向的数据可以更改
	//p = &b;//错误:不能修改常量指针p指向的地址

	printf("%d\n",*p);


	return 0;
}

const修饰指针和数据(常量指针,指向常量数据)

就是*两边都有const关键字进行修饰的

#include<stdio.h>

int main() {

	//const修饰指针和数据(常量指针,指向常量数据)
	int a = 10;
	int b = 20;
	const int* const p = &a; // p是一个常量指针,指向常量数据,既不能修改p的值,也不能通过p修改*p的值

	// *p = 20; // 错误!不能修改*p指向的值
	// p = &b; // 错误!不能修改常量指针p的值

	printf("%d\n", *p); // 输出10


	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值