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 = #
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 = π
*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;
}