由浅到深认识C语言(8)

news2024/12/29 11:35:41

该文章Github地址:https://github.com/AntonyCheng/c-notes

在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址:https://blog.csdn.net/AntonyCheng/article/details/136555245),该模板集成了最常见的开发组件,同时基于修改配置文件实现组件的装载,除了这些,模板中还有非常丰富的整合示例,同时单体架构也非常适合SpringBoot框架入门,如果觉得有意义或者有帮助,欢迎Star & Issues & PR!

上一章:由浅到深认识C语言(7)

8.指针的概念与应用

8.1.关于内存

存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分;

外存:又叫外部存储器,长期存放数据,掉电不丢失数据,常用设备:硬盘,flash,rom,u盘,光盘,磁带;

内存:又叫内部存储器,暂时存放数据,掉电数据丢失,常见设备:ram,DDR;

内存作用:

  • 内存是沟通CPU与硬盘的桥梁;
  • 暂存放CPU中的运算数据;
  • 暂存与硬盘等外部存储器交换的数据;

内存的分类:

  • 物理内存:内存物理硬件;
  • 虚拟内存:操作系统虚拟处理的内存;

注意:

  • 操作系统会在物理内存和虚拟内存之间做映射;
  • 在32位系统下,每个进程的寻址地址是4G:0x00 00 00 00 ~ 0xff ff ff ff;
  • 写程序时,我i们操作的是虚拟空间;
  • 在运行程序时,操作系统会将虚拟内存进行分区:
    • 堆:在动态申请内存的时候,在堆里开辟内存;
    • 栈:主要存放局部变量;
    • 静态全局区:
      1. 未初始化的静态全局区;
      2. 初始化的静态全局区;
    • 代码区:存放代码程序;
    • 文字常量区:存放常量;
  • 内存是以字节为单位来存储数据,可以将程序中的虚拟寻址空间看成一个很大的一维字符数据(因为字符是一字节的);

8.2.指针的概念

操作系统给内存的每个存储单元分配了一个编号,32位系统从 0x00 00 00 00 ~ 0xff ff ff ff ,64位系统从 0x00 00 00 00 00 00 00 00 ~ 0xff ff ff ff ff ff ff ff ,这个编号称之为地址,而指针就是地址;指针很重要,甚至可以通过相邻指针来推算数据类型,需要结合 C 语言中的数据类型所占内存来进行操作;

以64位为例, 2 64 2^{64} 264如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:我们认为逻辑地址是从 1 开始的,而物理地址是从 0 开始的,当物理地址转换成虚拟地址之后,也是从 0 开始的,也就是说,我们能打印出来的指针范围介于 0 ~ 18446744073709551615之间:

#include<stdio.h>

static void test() {
	int* a = 0;
	int* b = 18446744073709551615;
	printf("%p\n", a);
	printf("%p\n", b);
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然而,指针也可以为负数,但是不要让它为负数,因为负地址中存放着系统数据,例如 bios 系统等;

8.3.指针变量

概念

本质是一个变量,但是并不存放普通的数据类型,而是存放一个地址编号,即存放指针;

所占内存大小:

16位系统指针变量所占大小为:2B 示例:0x00 00 ==> 二字节;

32位系统指针变量所占大小为:4B 示例:0x00 00 00 00 ==> 四字节;

64位系统指针变量所占大小为:8B 示例:0x00 00 00 00 00 00 00 00 ==> 八字节;

以64位系统为例:

#include<stdio.h>

static void test() {
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(short*));
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(float*));
	printf("%d\n", sizeof(double*));
	printf("%d\n", sizeof(char***));
	return;
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:地址编号不能决定指针变量所占字节,始终是 8B;

变量的定义

定义指针变量的步骤:

  1. * 修饰指针变量名;
  2. 明确所保存变量的数据类型(前提),去定义一个普通变量;
  3. 从上往下整体替换,* 挨着数据类型;

学习必知:取地址操作是 &变量名

演示如下:

//stepOne
*p;
//stepTwo
int num;
//stepThree
int* p;

实例初识:

#include<stdio.h>

static void test() {
    //由系统自动分配一个合法的空间
	int num = 10;
    //按需求创建指针
	int* p;
    printf("%p\n", &num);
    //&num代表的是num的首地址
	p= &num;
    printf("%p\n", p);
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

以上程序会在内存里发生如下过程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:由上得知一个字节的内存都会有一个地址,而一个数据类型,例如 int 有 4 个字节,所以在 int 里会有 4 个地址,而 & 该数据变量时,接收到的只是首地址;

变量的使用

对保存的地址空间进行读写操作;

使用中,*p 表示取 p 所保存的地址编号对应的空间内容; ==> 指针变量 p 的解引用;

知识点梳理:

#include<stdio.h>

static void test() {
	int num = 10;
	int* p = &num;
	//以下是各个变量使用时所代表的内容
	//num代表10这个整数
	//&num代表num整型变量的首地址值
	//p代表num整形变量的首地址值
	//&p代表p指针变量的首地址值
	//*p代表10这个整数
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

输出示例如下:

#include<stdio.h>

static void test() {
	int num = 10;
	int* p = &num;
	printf("&num = %p\n", &num);
	printf("p = %p\n", p);
	printf("*p = %p\n", *p);      	//对于*p的使用
	printf("*p int = %d\n", *p);	//对于*p的使用	
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入示例如下:

#include<stdio.h>

static void test() {
	int num = 10;
	int* p = &num;
	printf("请输入要改变的值:");
	scanf_s("%d", p);
	printf("*p = %p\n", *p);
	printf("*p int = %d\n", *p);
    printf("num = %d\n",num);
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

变量的类型

这里区分两种类型 { 指针变量自身的类型 指针变量所指向的类型 \bf{这里区分两种类型} \begin{cases} \bf{指针变量自身的类型} \\ \bf{指针变量所指向的类型} \end{cases} 这里区分两种类型{指针变量自身的类型指针变量所指向的类型

区分方法:

  • 指针变量自身的类型就是把变量名给删去,即 int* p 的自身类型是 int*
  • 指针变量所指向的类型就是把变量名和离此变量名最近的第一个 * 给删去,即 int* p 所指向的变量是 int
指针是按照1B内存排列的,指针变量所指向的类型决定了指针取值的宽度和跨度
指针的排序顺序是由系统决定的,但常见的都是逆序,取值后会被自动升序

排序顺序:

0x040x030x020x01

示例一: 指针变量取值宽度;

宽度指的是从目标部分首地址开始往后取地址的固定宽度(地址是按照1B内存排列的,固定宽度指的是所指向类型的宽度,即由所指向类型长度决定);

#include<stdio.h>

static void test() {
	int num = 0x01020304;  	//这里有4B内存
	int* p1 = &num;			//int往后取的是4B宽度
	short* p2 = &num;		//short往后取的是2B宽度
	printf("*p1 = %#x\n", *p1);
	printf("p2 = %#x\n", *p2);  //证明指针排序和取值顺序问题
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例二: 指针变量取值跨度;

跨度指的是从变量首地址开始往后取到目标部分前一个地址的单位长度(单位长度指把所指向类型的长度看成一个单位,即int的单位长度就是4B,int的两个单位长度就是8B,即由指针所指向类型长度决定);

#include<stdio.h>

static void test() {
	int num = 0x01020304;
	short* p1 = &num;
	int* p2 = &num;
	printf("pOfNum = %d\n", p1);
	printf("pOfNum'sShort = %d\n", p1 + 1);
	printf("pOfNum'sInt = %d\n", p2 + 1);
	printf("pOfNum'sInts(2) = %d\n", p2 + 2);
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关于宽度和跨度图解:

0x040x030x020x01

若要取 0x0203 ,则 0x04 这1B长度就是跨度,而 0x02 和 0x03 这2B长度就是宽度;

综合案例:

现有 int num = 0x01020304; ,要求自定义变量取出 0x0102 ;

#include<stdio.h>

static void test() {
	int num = 0x01020304;	//内存中存的顺序是 04 03 02 01
    //第一步:这里将指针从04首移到了03尾,然后按照short长度升序排成 03 04
	short* p = &num;    	
    //第二步:这里又将指针从03尾(02首)移到了01尾,然后按照short长度升序排成 01 02
	short* p1 = p + 1;
    //这里的第一步和第二步可以调换;
	printf("%#x", *p1);
}

int main(int argc, char* argv[]) {
	test();
	return 0;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时,跨度和宽度相等,如果跨度和宽度不相等,就需要用到类型转换,且选择宽度和跨度中最小的那个值作为新指针变量的长度(单位长度的倍数);

类型转换:

示例如下:

我们再去取 0x0203

#include<stdio.h>
void test() {
	int a = 0x01020304;
	char* p = &a;
	short* p1 = (short*)(p + 1);
	printf("%#x", *p1);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

变量的初始化

如果局部指针变量不初始化,保存的是随机的地址编号,记住千万不要操作,因为会导致“断错误”,用打印来演示:

#include<stdio.h>
void test() {
	int* p;
	printf("001\n");
	printf("%d\n", p);  //vs中会直接报错,在vc++中可以运行,但是一旦运行到这里,程序会自动结束;
	printf("002\n");
}

int main(int argc, char* argv[]) {
	test();
	return;
}

所以我们要进行指针变量的初始化,我们不想让指针变量指向任何地方,可以初始化为 NULL,而 NULL 指针就是 0 地址,从 0 地址到某一地址中包含着很多系统底层的数据,所以记住这里也不能操作,若操作,也会导致“断错误”;

int* p = NULL;
//注意这里是 NULL(大写),因为这里运用到了宏,宏规则上是大写;

示例如下:

#include<stdio.h>
void test() {
	int* p = NULL;
	printf("001\n");
	printf("%p\n",p);
	printf("002\n");
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正常的初始化:

为了将指针变量初始化为合法的地址,则应该让指针变量存入合法的地址;

int num = 10;
int* p = &num;

使用变量须知

取地址符&和指针解引用符*区别:

对一个变量取地址 &+变量名 ,整个表达式的类型就是 **变量的类型 + * **;

对一个指针变量取星花 *+变量名 ,整个表达式的类型就是 指针变量的类型 - *

如果 *& 同时存在,可以互相抵消,规则如下:

==当 ***** 和 & 交错出现时,按顺序抵消即可,如例 *&*&p ==> p*&*&*p ==> *p&*&p > &p

当 ***** 和 & 非交错出现时,不能出现 && 这种情况;

除有一种排列 外, ***** 和 & 的数量必须相同,即化简结果始终为 p这一种排列 就是 *&**&p 这种排列,整个符号项内只能出现这一次 &**& 这种排列,即化简结果始终为 *p 或者 p

论证:*p == num;

#include<stdio.h>
void test() {
	int num = 10;	//num = 10;
	int* p = &num;	//p = &num
	//*p == *&num; ?
	if (*p == *&num) {
		printf("*p = %d\n", *p);
		printf("*&num = %d\n", *&num);
		printf("num = %d\n", num);
		printf("*p == *&num == num\n");
	}
	else {
		printf("no_equal");
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

指针注意事项

  • void 不能定义变量,但 void* 可以定义指针变量;

    因为普通变量用void定义,系统不知道其大小;但是指针的大小在系统里是固定的,即系统知道其大小;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    该指针叫万能指针,它可以保存任意一级指针:

    #include<stdio.h>
    void test() {
    	void* p;
    	char ch;
    	p = &ch;
    	int num;
    	p = &num;
    	float f;
    	p = &f;
    }
    
    int main(int argc, char* argv[]) {
    	test();
    	return;
    }
    

    但是万能指针不能直接引用其 *p ,因为 void 所指向的是没有长度的数据,即不知道它的宽度;

    #include<stdio.h>
    void test() {
    	int num = 10;
    	void* p = &num;
    	printf("%d", *p);
    }
    
    int main(int argc, char* argv[]) {
    	test();
    	return;
    }
    

    报错如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    所以我们应该实现对万能指针进行强制类型转换;

    #include<stdio.h>
    void test() {
    	int num = 10;
    	void* p = &num;
    	printf("%d", *((int*)p)); //p 临时的指向类型为 int,系统能确定宽度为 4B
    }
    
    int main(int argc, char* argv[]) {
    	test();
    	return;
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • *不要对没有初始化的指针变量取

    在 vc++ 中不会出现语法错误,但是会出现“断错误”,在 vs 中会直接报错;

    因为 p 没有初始化,内容随机,也就是 p 指向了一个位置空间,系统不允许用户取值 *p 操作;

  • *不要对初始化为 NULL 的指针变量取

    在 vc++ 中不会出现语法错误,但是会出现“断错误”,在 vs 中会直接报错;

    因为 NULL 处的地址是(void*)0 地址,也就是内存的起始地址,这是受系统保护的;

  • 不要给指针变量赋普通的数值;

    例如 int* p = 1000; 在 vc++ 和 vs 中不会出现语法错误,但是这一个地址我们 99.99% 的概率没有声明,也就是该位置是非法的,所以会出现“段错误”,所以不能使用 *p;

  • 指针变量不要操作越界的空间;

    示例如下:

    #include<stdio.h>
    void test() {
    	char ch = 10;
    	int* p = &ch;
    	printf("p = %d", *p);
    }
    
    int main(int argc, char* argv[]) {
    	test();
    	return;
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    因为 char 所占空间是 1B ,而 int 所占空间是 4B,这样一取值就超过了 char 的地址,这样就造成了越界,越界了 3B;

8.4.数组元素的指针

图解如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

引例一:

定义一个指针变量,保存 arr 数组首元素的地址;

#include<stdio.h>
void test() {
	int arr[] = { 10,20,30,40,50 };
	int* p = &arr[0]; //这里是目标代码
	printf("arr[%d] = %d", 0, *p);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

引例二:

通过数组元素的指针变量遍历数组的元素;

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int n = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0]; //整体循环的前提就是p的初始化为首地址
	for (int i = 0; i < n; i++) {
		printf("%d ",*(p++));
		/*
		printf("%d ",*(p + i));
		*/
		/*
		printf("%d ",*p);
		p++
		*/
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

引例三:

通过数组元素的指针变量给数组的元素获取键盘输入;

#include<stdio.h>
void test() {
	int arr[5]={0};
	int n = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	printf("请输入%d个整数,存入数组:\n",n);
	for (int i = 0; i < n; i++) {
		scanf_s("%d", p++);
		/*
		scanf_s("%d", p + i);
		*/
	}
	p = &arr[0];
	printf("你的数组如下\n");
	for (int i = 0; i < n; i++) {
		printf("%d ", *p++);
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数组的 [] 和 *() 关系

引入如下代码:

int arr[5]={10,20,30,40,50};
int n = sizeof(arr) / sizeof(arr[0]);
//数组名 arr 作为地址代表的是首元素地址
//数组名 arr 作为类型代表的是数组总大小

在早期的 C 语言中是没有 [] 这个标识的,大都用 *() ,即 arr[1] 等价于 *(arr+1),然而 *(arr+1) 也可以写作 *(1+arr) ,即可推出 *(1+arr) 等价于 1[arr]

所以我们可以凭借着上述表达来解释一个很重要的问题:为什么 arr 数组名代表首元素地址?

首元素地址为 &arr[0] ==> &*(arr+0) ==> arr+0 ==> arr

所以当我们对一个数组元素进行初始化时,我们可以直接如下操作:

int arr[5] = {0};
int* p = arr;

示例如下:

#include<stdio.h>
void test() {
	int arr[5] = { 0 };
	int n = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	printf("请输入%d个整数,存入数组:\n", n);
	for (int i = 0; i < n; i++) {
		scanf_s("%d", p++);
	}
	p = arr;
	printf("你的数组如下\n");
	for (int i = 0; i < n; i++) {
		printf("%d ", *p++);
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

arr 和 &arr 的区别

区别如下:

  • arr 是数组的首元素地址

    arr 是以指针变量所指向的数据类型长度为单位,即 arr+1 所跳过的是第一个数组元素;

    数组名 arr 也是一个符号常量(类似于宏),它不能被赋值,自加和自减等;

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • &arr 是数组的首地址

    &arr 是以整个数组的数组长度为单位,即 &arr+1 所跳过的是整个数组;

相同点如下

arr&arr 所代表的地址值一样;

图解如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例如下:

#include<stdio.h>
void test() {
	int arr[5] = { 10,20 };
	printf("%d 和 %d\n", arr, arr + 1);
	printf("%d 和 %d\n", &arr, &arr + 1);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

指向同一数组的两指针关系

指向同一数组的两个指针变量相减,返回的是相差元素的个数;

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int* p1 = arr;
	int* p2 = arr + 3;
	int* p3 = p2 - p1;
	printf("%d", p3);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

指向同一数组的两个指针变量不能相加,原因是指针变量很大(电话号码),相加的话直接就越界了;

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int* p1 = arr;
	int* p2 = arr + 3;
	int* p3 = p1 + p2;
	printf("%d",p3);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

报错效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

指向同一数组的两个指针变量可以比较大小;

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int* p1 = arr;
	int* p2 = arr + 3;
	if (p1 > p2) {
		printf("P1>P2");
	}
	else {
		printf("P2>=P1");
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

指向同一数组的两个指针变量可以互相赋值;

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int* p1 = arr;
	int* p2 = arr + 3;
	p1 = p2;
	printf("p1 = %u\np2 = %u", p1, p2);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在不越界的情况下 [] 中的内容可以为负数;

引用以上所学 *() == [] ;

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int* p1 = arr;
	int* p2 = arr + 3;
	printf("%d", p2[-2]); //p2[-2] == *(p2 - 2)
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

题目案例

请回答以下打印结(*++ 同优先级,运算时自右向左):

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int* p = arr;
	printf("%d\n",*p++);
    // ++ 在右边时先使用,后加减,所以这里会先使用去取 * 然后赋值给 %d ,即 10 ,注意在同一个程序里面,p 已经移动了一次指针,即 *p = 20;
	printf("%d\n",(*p)++);
    // 小括号优先级别最高,++ 在右边时先使用,后加减,所以这里是先计算 %d == *p == 20 ,又注意在同一程序里,*p 已经 ++ 了一次,即此时 *p = 21;
	printf("%d\n",*(p++));
    // 小括号优先级别最高,++ 在右边时先使用,后加减,所以这里是先计算 %d == *p == 21 ,又注意在同一程序里,p 已经移动了一次指针,即 *p = 30;
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.5.指针数组

指针数组:本质是数组,只是每个元素是指针;

图解如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例如下

#include<stdio.h>
void test() {
	int arr1[5] = {10,20,30,40,50};
	int* arr2[5];
	int i = 0;
	for (i = 0; i < 5; i++) {
		arr2[i] = &arr1[i];
	}
	for (i = 0; i < 5; i++) {
		printf("%d ", *arr2[i]);
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

字符指针数组

**注意:**以下情况在不同的编译器中可能有不同,这是以 VS 为例,其他编译器需要视情况更改代码;

字符数组中是不可以包含多个字符串的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是在字符指针数组中,可以包含多个字符串;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时指针存放的是每一个字符串的首元素地址,打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

即:*ch[i] 就会遍历数组元素中每个数组的首元素;

注意,这里的首元素是 char 类型;

通过结合对数组单元名和字符串名的理解可知:字符串名(这里要将一个字符数组看成一个整体字符串)代表的是整个字符串的首元素地址,而这里的数组单元名也代表的整个字符串的首元素地址,所以我们可以通过遍历数组单元名来遍历一个字符串数组,诚然,二维字符数组和这种方式大同小异,但是这种方式能够更形象地解决 C 语言中没有 String数组 概念的问题;

示例如下:

#include<stdio.h>
void test() {
	char* ch[3] = { "abc","def","gih" };
	for (int i = 0; i < 3; i++) {
		printf("%s ",ch[i]);
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就如上例,如果我们只想输出 abc 中的 b ,二维数组可以通过更换深层遍历数值实现,而这里需要知道 b 的地址,由指针所学可知,通过在指针上做加运算就能实现;

#include<stdio.h>
void test() {
	char* ch[3] = { "abc","def","gih" };
	printf("%c ", *ch[0] + 1);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.6.数组指针

数组指针

本质就是指针变量,只是该变量保存的是数组的首地址,即指向数组的指针,因为它是指针,所以它的大小是 8B(64位系统);

基本格式举例

int arr[5] = {10,20,30,40,50};
int (*p)[5] = arr;

必须加上小括号,因为 [] 的优先级大于 *

变量数据类型int* []

虽然指针变量 p 的大小是 8B (64位系统),但是此时的 p+1 所跳过的大小是整个数组(条件:数组指针变量和需要操作的数组变量数值个数相同,即中括号中的数值大小相同),p 的跳转大小和需要操作的数组整体大小没关系,仅和自己的数组变量个数和数据类型有关;

图解:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例如下:

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int(*p)[5] = NULL;				//这里就证明跳转大小和需要操作的数组整体大小没关系
	printf("%p\n", p);
	printf("%p\n", p + 1);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

若我们想用数组指针去遍历一个数组中的数,我们需要理解一下 int(*p)[5] 的含义,当我们已经用这个指针指向了一个长度为 5 的数组时,即 int(*p)[5] = arr; ,此时数组指针 (*p) 已经指向了数组的首元素地址,且指针变量宽度为 5 个 int 长度,沿着这种思路,我们将 int(*p)[5] = arr; 改成 int(*p)[1] = arr; 此时就表示指针 (*p) 已经指向了数组的首元素,且指针变量宽度为 1 个 int 长度,我们就可以移动该指针 (*p)+1 以便于达到单个遍历的效果,如果我们想要改变它的遍历起始位置,则需要改变被操作数组的起始值,即赋予我们指针一个非首元素的地址,但此时要注意 (*p)+1 依然是一个指针,所以要去取它的值的话,需要整体取 * ;

使用数组指针单个遍历示例如下:

#include<stdio.h>
void test() {
	int arr[5] = { 10,20,30,40,50 };
	int length = sizeof(arr) / sizeof(arr[0]);
	int(*p)[1] = arr;
	for (int i = 0; i < length; i++) {
		printf("%d ", *(*p + i));
	}
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样就可以把二维数组看成只有一行的一维数组:

细细转换一下可以发现:*(*p+1) ==> *(*p(0)+1) ==> *(p[0]+1) ==> p[0][1] ♥♥♥

数组指针应用

数组指针的应用多在于二维数组,接下来就将二维数组的性质和数组指针的性质相结合;

图解如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一定要注意,对行地址取完 * 之后依然是地址,只是变成了列地址;

案例:

对于一个数组,用普通变量和指针变量去查找;

#include<stdio.h>
void test() {
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	//查询 6 的位置;
	printf("普通变量查找到:%d\n", arr[1][1]);
	printf("指针变量查找到:%d\n", *(* (arr + 1) + 1));
	//遍历第二行地址
	int length = sizeof(arr[1]) / sizeof(arr[1][0]);
	int i;
	printf("普通变量遍历这个数组第二列是:");
	for (i = 0; i < length; i++) {
		printf("%d ", arr[1][i]);
	}
	printf("\n");
	printf("指针变量遍历这个数组第二列是:");
	for (i = 0; i < length; i++) {
		printf("%d ", *(*(arr + 1) + i));
	}
	printf("\n");
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

常用的一些遍历变形如下:

*arr + 2 ==> 第二列的列地址;

arr[1] ==> *(arr + 1) 第一行第零列的列地址;

&arr[0] + 2==> &*(arr + 0) + 2 == arr + 2 第二行的行地址;

**arr ==> *(*(arr+0)+0) == arr[0][0] 第一行第一列;

我们再用指针完完整整写一个二维数组的遍历:

#include<stdio.h>
void test() {
	int arr[3][4];
	int num = sizeof(arr) / sizeof(arr[0]);
	int length = sizeof(arr[1]) / sizeof(arr[1][0]);
	int i,j;
	for (i = 0; i < num; i++) {
		for (j = 0; j < length; j++) {
			scanf_s("%d", &*(* (arr + i)+j));
		}
	}
	//查询 6 的位置;
	printf("普通变量查找到:%d\n", arr[1][1]);
	printf("指针变量查找到:%d\n", *(* (arr + 1) + 1));
	//遍历第二行地址
	printf("普通变量遍历这个数组第二列是:");
	for (i = 0; i < length; i++) {
		printf("%d ", arr[1][i]);
	}
	printf("\n");
	printf("指针变量遍历这个数组第二列是:");
	for (i = 0; i < length; i++) {
		printf("%d ", *(*(arr + 1) + i));
	}
	printf("\n");
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

任何维度的数组在物理存储上都是一维的,但是为了便于人们分析,将一维数组逻辑化为了多维,而指针就还原了物理存储上的一维,即用 arr+数字 跨越每一行的长度并指向终点行地址,用 *(arr+数字)+数字 跨越每一行中每一列的长度并指向具体列地址;

所以我们可以把二维数组用一维循环去遍历:

#include<stdio.h>
void test() {
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	int length1 = sizeof(arr) / sizeof(arr[0]);
	int length2 = sizeof(arr[0]) / sizeof(arr[0][0]);
	int i;
	for (i = 0; i < length1 * length2; i++) {
		printf("%d ", *((*arr )+i));
	}
	printf("\n");
	for (i = 0; i < length1 * length2; i++) {
		printf("%d ", *(*arr + i));
	}
	printf("\n");
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

根据上例思考一下,为什么这两种写法顺序不同,但是能达到一样的效果?此时我们应该想一想 i 的宽度,因为此时遍历的是一组一维的 int 类型数据,这是底层原有的模样,所以 i 的宽度应该是 4B,所以只要出现 +1 ,就代表指针往后跳跃了 4 个单位,所以就会忽略二维结构规则,进而原始输出;

8.7.多级指针

图解如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1 级指针变量保存 0 级指针变量(普通变量)的地址;
……
1+n 级指针变量保存 n 级指针变量(普通变量)的地址;
反取数值时,几级指针变量上取几个 *

示例如下:

#include<stdio.h>
void test() {
	int n = 10;
	int* p1 = &n;
	printf("%d\n", *p1);
	int** p2 = &p1;
	printf("%d\n", **p2);
	int*** p3 = &p2;
	printf("%d\n", ***p3);
}

int main(int argc, char* argv[]) {
	test();
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.8.指针作为函数的参数

如果需要在函数内部修改外部变量的值,就需要将外部变量的地址传递给函数(以指针作为函数参数);

先举一个例子:

我们用空值函数对调两个 int 型整数;

错误示例:

#include<stdio.h>
void method(int a,int b) {
	int temp = a;
	a = b;
	b = temp;
}

int main(int argc, char* argv[]) {
	int a = 1;
	int b = 2;
	method(a,b);
	printf("a = %d;b = %d\n", a, b);
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于实参传送数值,形参会在拿到数值后在内存空间中创建属于自己的地址,所以在函数中所做出的所有改变都不是对实参做出的操作,而是对形参,所以我们不应该传送数值,而应该传输地址;

正确示例:

#include<stdio.h>
void method(int *a,int *b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main(int argc, char* argv[]) {
	int a = 1;
	int b = 2;
	method(&a,&b);
	printf("a = %d;b = %d\n", a, b);
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再举一个例子:

在函数内部给空指针 p 赋值;

#include<stdio.h>
//因为在main中传入的*p本就是一个一级*,所以要用二级*去接收它
void test(int**p) {  
	static int num = 10;
	*p = &num;
}

int main(int argc, char* argv[]) {
	int* p = NULL;
	test(&p);
	printf("*p = %d\n", *p);
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再次总结

如果想在函数内部修改外部变量的值,就需要将外部变量的地址传递给函数,以指针变量作为形参,而且如果想要传入地址,那么必须让形参指针级别比实参指针级别高一级别,比如要传入 int num = 10; 那么实参传入的就是 int 型,所以要用 int* 型去接收,再比如要传入 int* num = NULL ,那么实参传入的就是 int* 型,所以要用 int** 型去接收,依次类推;

但在结构体中,该方法会失效;

所以我们应该理清楚传参的特点,我们应当注意,基本类型变量名不能代表地址,即使该变量名前面有 * 修饰也不行,所以当我们传入基本类型变量名时,都应当加上 & 取地址符号,所以在函数内要用更高一级 * 作为函数的形参;但是对于引用类型变量名来说,它们的就可以代表地址,所以传参时不用加上 * 取地址符号,所以在函数内不需要用更高一级 * 作为函数的形参;

8.9.一维数组名作为函数参数

如果函数内部想操作(读和写)外部数组的元素,请将外部数组的数组名传入函数;

一维数组,以 int arr[5] 为例,传入函数后会被自动优化成 int arr* ,所以以后要将一维数组传入函数时,务必使用指针形式传入;

示例如下:

#include<stdio.h>
void test(int*arr,int n) {  
	for (int i = 0; i < n; i++) {
		scanf_s("%d", &*(arr + i));
	}
}

int main(int argc, char* argv[]) {
	int arr[5];
	int n = sizeof(arr) / sizeof(arr[0]);
	test(arr,n);
	for (int i = 0; i < n; i++) {
		printf("%d ", arr[i]);
	}
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.10.二维数组名作为函数参数

二维数组名作为函数参数和一维数组大同小异;

无论是几维数组,数组名作为函数的形参时,都会被优化成数组指针,且只有话离数组名最近的那个遍历位;
int arr[3] ==> int* p;
int arr[3][4] ==> int* p[4];
int arr[3][4][5] ==> int* p[4][5];
int arr[3][4][5][6] ==> int* p[4][5][6];

二维数组名最为函数参数示例如下:

#include<stdio.h>
void test(int*arr,int hang_length,int lie_length) {  
	printf("请输入你的数组:");
	for (int i = 0; i < hang_length * lie_length; i++) {
		scanf_s("%d", &*(arr + i));
	}
}

int main(int argc, char* argv[]) {
	int arr[3][4];
	int hang_length = sizeof(arr) / sizeof(arr[0]);
	int lie_length = sizeof(arr[0]) / sizeof(arr[0][0]);
	test(arr,hang_length,lie_length);
	for (int i = 0; i < hang_length; i++) {
		printf("第%d列", i + 1);
		for (int j = 0; j < lie_length; j++) {
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

8.11.指针作为函数的返回值

函数不要返回普通局部变量的地址,因为普通局部变量的生命周期是整个函数,函数一结束,该变量就会被回收,届时返回的那个地址再次使用时就会出现很严重的错误,解决办法就是将该变量定义成静态变量或者是全局变量

所以我们可以用函数的返回值来更改函数外部的变量值:

#include<stdio.h>
int* test() {  
	static int num = 10;
	return &num;
}

int main(int argc, char* argv[]) {
	int* p = NULL;
	p = test();
	printf("%d\n", *p);
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

相比于将指针作为参数去修改的方式,返回值的方式更值得推荐;

8.12.函数指针

函数名代表的是函数的入口地址;

**定义:**函数指针本质是指针,保存的是函数的入口地址;

所以函数名和函数指针就是大名和小名的关系;

基本格式:

为了防止指针变量和小括号结合,所以得用小括号将 * 和指针变量括起来;
指针变量所指向的函数是什么样的参数数据类型,小括号里就应该是什么样的参数数据类型;
指针变量所指向的函数必须有返回值,而且该返回值和指针变量所指向的数据类型一样;
普通函数变量调用和指针函数变量调用的方式一模一样;

int test(int a,int b){}
int(*p)(int, int) = NULL;

示例如下:

#include<stdio.h>
int test(int a,int b) {  
	return a + b;
}

int main(int argc, char* argv[]) {
	int(*p)(int, int) = NULL;
	p = test;
	printf("&test = %u\n", test);
	printf("&p = %u\n", p);
	printf("test() = %d\n", test(10, 20));
	printf("p() = %d\n", p(10, 20));
	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

函数指针参数

函数指针作为函数参数示例如下:

用函数指针去创建一个能够加减乘的整数计算器程序;

#include<stdio.h>
int add(int a, int b) {
	return a + b;
}
int sub(int a, int b) {
	return a - b;
}
int mul(int a, int b) {
	return a * b;
}

int my_calc(int a, int b, int (*p)(int, int)) {
	return p(a, b);
}
int main(int argc, char* argv[]) {
	int a, b;
	printf("请输入两个要操作地数:\n");
	scanf_s("%d %d", &a, &b);
	printf("加法计算结果:%d\n", my_calc(a, b, add));
	printf("减法计算结果:%d\n", my_calc(a, b, sub));
	printf("乘法计算结果:%d\n", my_calc(a, b, mul));

	return;
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下一章:由浅到深认识C语言(9)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1520689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AHU 汇编 实验六

一、实验名称&#xff1a;实验6 输入一个16进制数&#xff0c;把它转换为10进制数输出 实验目的&#xff1a; 培养汇编中设计子程序的能力 实验过程&#xff1a; 源代码&#xff1a; data segmentbuff1 db Please input a number(H):$buff2 db 30,?,30 dup(?),13,10buff3 …

PytorchAPI的使用及在GPU的使用和优化

API 调用API&#xff1a;和手动实现的思路是一样的。#1&#xff0c;#2这两个步骤是通用的步骤&#xff0c;相当于建立一个模型&#xff0c;之后你具体的数据直接丢进去就行了。只需要按着这样的样式打代码就行&#xff0c;死的东西&#xff0c;不需要你自己创造。 import torc…

力扣串题:反转字符串

特殊思路&#xff1a;k的重新赋值&#xff1a;当再加就超过字符串的长度时&#xff0c;需要把k变为字符串的剩余长度&#xff0c;不必理会当剩余长度介于k和2k之间的部分 char* reverseStr(char* s, int k) {for(int i0;i<strlen(s);ii2*k){kik>strlen(s)? strlen(s)-i…

三维卡通数字人解决方案,支持unity和Maya

企业对于品牌形象和宣传手段的创新需求日益迫切&#xff0c;美摄科技凭借其在三维卡通数字人领域的深厚积累&#xff0c;推出了面向企业的三维卡通数字人解决方案&#xff0c;帮助企业轻松打造独具特色的虚拟形象&#xff0c;提升品牌影响力和市场竞争力。 美摄科技的三维卡通…

服务器遭遇挖矿病毒syst3md及其伪装者rcu-sched:原因、症状与解决方案

01 什么是挖矿病毒 挖矿病毒通常是恶意软件的一种&#xff0c;它会在受感染的系统上无授权地挖掘加密货币。关于"syst3md"&#xff0c;是一种特定的挖矿病毒&#xff0c;它通过在受感染的Linux系统中执行一系列复杂操作来达到其目的。这些操作包括使用curl从网络下载…

汇总全网免费API,持续更新(新闻api、每日一言api、音乐。。。)

Public&FreeAPI 网址&#xff1a;apis.whyta.cn &#xff08;推荐&#xff09; UomgAPI 网址&#xff1a;https://api.uomg.com 教书先生 网址&#xff1a;https://api.oioweb.cn/ 山海API https://api.shserve.cn/ 云析API铺 https://api.a20safe.com/ 韩小韩…

AI智能分析网关V4将HTTP消息推送至安防监控视频汇聚EasyCVR平台的操作步骤

TSINGSEE青犀视频智能分析网关V4内置了近40种AI算法模型&#xff0c;支持对接入的视频图像进行人、车、物、行为等实时检测分析&#xff0c;上报识别结果&#xff0c;并能进行语音告警播放。硬件管理平台支持RTSP、GB28181协议、以及厂家私有协议接入&#xff0c;可兼容市面上常…

AI_寻路系统_基本寻路

学习笔记&#xff0c;仅供参考&#xff01; 1、创建了一个新的第三人称项目&#xff0c;重命名为navsystem1. 2、将使用 寻路网格体边界体积&#xff08;Navigation Mesh Bounds Volume&#xff09; 来指定关卡中需要生成寻路的区域。代理将使用此信息在关卡中到达目的地。搜索…

浏览器同源策略及跨域问题

同源策略&#xff1a;同源策略是一个重要的安全策略&#xff0c;它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档&#xff0c;减少可能被攻击的媒介。 同源策略的作用&#xff1a;保护浏览器中网站的安全&#xff0c;限制ajax只…

汽修门店管理系统,打造高效汽修服务【汽修店软件管理系统】

在汽车后市场&#xff0c;汽修门店的运营效率直接影响着客户的满意度和店铺的利润。为了提升服务质量和工作效率&#xff0c;越来越多的汽修店开始引入汽修门店管理系统。那么&#xff0c;如何搭建汽修店软件管理系统&#xff0c;汽修门店管理系统怎么使用。 首先&#xff0c;我…

python基础——元组【特点,创建,常见操作方法:index,len,count】

&#x1f4dd;前言&#xff1a; 上一篇文章python基础——列表讲解了关于列表的先关知识&#xff0c;还介绍了很多列表的常见操作方法&#xff0c;这篇文章我们就基于上篇文章带大家认识的有关序列的知识&#xff0c;继续讲解python中另一种常见的数据容器——元组&#xff1a;…

(学习日记)2024.03.12:UCOSIII第十四节:时基列表

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

分布式与集群,二者区别是什么?

&#x1f413;分布式 分布式系统是由多个独立的计算机节点组成的系统&#xff0c;这些节点通过网络协作完成任务。每个节点都有自己的独立计算能力和存储能力&#xff0c;可以独立运行。分布式系统的目标是提高系统的可靠性、可扩展性和性能。 分布式服务包含的技术和理论 负…

Linux学习笔记:什么是文件描述符

什么是文件描述符 C语言的文件接口文件的系统调用什么是文件描述符,文件描述符为什么是int类型?为什么新打开的文件的文件描述符不是从0开始? 文件描述符 fd (file descriptor) C语言的文件接口 当时学习C语言的时候,学习了文件接口 具体可以查看之前的文章: 链接:C语言的文…

Elasticsearch:从 Java High Level Rest Client 切换到新的 Java API Client

作者&#xff1a;David Pilato 我经常在讨论中看到与 Java API 客户端使用相关的问题。 为此&#xff0c;我在 2019 年启动了一个 GitHub 存储库&#xff0c;以提供一些实际有效的代码示例并回答社区提出的问题。 从那时起&#xff0c;高级 Rest 客户端 (High Level Rest Clie…

DP-不同的二叉搜索树

给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;1提…

Unity URP 如何写基础的曲面细分着色器

左边是默认Cube在网格模式下经过曲面细分的结果&#xff0c;右边是原状态。 曲面细分着色器在顶点着色器、几何着色器之后&#xff0c;像素着色器之前。 它的作用时根据配置信息生成额外的顶点以切割原本的面片。 关于这部分有一个详细的英文教程&#xff0c;感兴趣可以看一…

3.15号arm

汇编语言 1. 汇编语言的组成 汇编文件中由伪操作、伪指令、汇编指令以及代码注释这几部分组成 伪操作&#xff1a; ARM的汇编中伪操作以.为前缀&#xff0c;所有的伪操作不占用内存空间&#xff0c;编译汇编时告诉编译器怎么编译当前文件&#xff0c;主要用来修改汇编内…

如何本地部署SeaFile文件共享服务并实现无公网IP访问内网本地文件

文章目录 1. 前言2. SeaFile云盘设置2.1 Owncould的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4.公网访问测试5.结语 1. 前言 现在我们身边的只能设备越来越多&#xff0c…

C++语法、Linux命令查询网站

文章目录 1.cplusplus2.cppreference3.Linux命令查询网站 1.cplusplus 网址&#xff1a;https://legacy.cplusplus.com/ 2.cppreference 1.cppreference中文网站&#xff1a;https://zh.cppreference.com/w/首页 2.cppreference英文原站&#xff1a;https://en.cppreference…