C语言中的基础指针操作
在C语言中,指针是一个非常重要的概念,它提供了直接访问内存地址的能力。指针变量用于存储内存地址,而不是数据值,在某种意义上和门牌号具有相似含义:指针是一个变量,其存储的是另一个变量的内存地址,这个内存地址唯一的标识,用于指向特定的内存位置。门牌号也是用来唯一标识一个具体的房屋或地址的,但指针的使用要复杂得多,涉及到内存的管理、指针的运算、野指针的避免等多个方面。在处理数组、字符串、动态内存分配以及函数参数传递等方面使得程序员们能够编写出更灵活、更高效的代码。
指针的概念
指针是一个变量,其值为另一个变量的地址,即直接指向内存中的某个位置,指针的声明需要在变量类型前加上星号*
,像int *ptr;
就声明了一个指向整数的指针变量ptr
。
指针的用途和功能
-
动态内存管理:C语言允许程序员在运行时动态地分配和释放内存,通过指针来实现,如使用
malloc
、calloc
和realloc
等函数分配内存,使用free
函数释放内存。 -
数组操作:指针可以用来遍历数组,因为数组名本质上是一个指向数组首元素的指针。使用指针进行数组操作比使用数组索引更加高效。
-
字符串处理:在C语言中字符串是通过字符数组实现的。因此,字符串操作(如复制、拼接等)可以通过指针操作来实现。
-
函数参数传递:通过使用指针作为函数参数,可以在函数内部修改外部变量的值,实现数据的双向传递。
-
指向函数的指针:指针也可以指向函数,这使得可以将函数作为参数传递给其他函数,或者通过指针调用函数。
-
指向指针的指针:C语言允许创建指向指针的指针,这在进行复杂数据结构(如链表、树等)的操作时非常有用。
指针操作的大概流程如下:
指针的基础操作
声明并初始化指针
在定义指针前需要先声明一个整数变量(我定义的value
)并初始化,随后声明一个指向整数的指针变量(我声明的是pointer
,很多人习惯使用p
)并初始化为value
的地址
int value = 10; int * pointer = &value;
在C语言中这里的三种写法都是可以的:
int* pointer = &value;
int * pointer = &value;
int *pointer = &value;
随后即可通过访问指针来查询这个整型变量(value
)的值:
printf("通过指针访问的值: %d \n", *pointer);
完整代码如下:
#include <stdio.h> int main() { int value = 10; int *pointer = &value; printf("通过指针访问的值: %d\n", *pointer); return 0;
}
输出内容:通过指针访问的值: 10
修改指针指向的值
在C语言中,如果已经声明并初始化了一个指针,可以做到只修改指针所指向的值,原变量的数值不会改变。就好比结婚一样,老实的Java程序员大锤和小美去民政局登记结婚(为一个变量声明并初始化了一个指针),但大锤满足不了小美了(原数值因为各种原因在某项功能中需要进行改动),小美又不想离婚(修改原变量值),于是小美就找到了老王来满足她(修改指针所指向的值),以后小美还可以找老陈、老宋、老李(多次修改指针所指向的值),但是这样会造成:1.大锤没法活了(程序崩溃
),2.孩子不是大锤亲生(野指针
),3.家丑外扬(内存泄漏
),4.小美被玩坏了(数据损坏
)。根据刚才的代码继续编写:
#include <stdio.h> int main() { int value = 10; int *pointer = &value; printf("通过指针访问的值: %d\n", *pointer); // 修改指针指向的值 *pointer = 20; printf("修改后通过指针访问的值: %d\n", *pointer); printf("直接访问变量value的值: %d\n", value); return 0;
}
输出内容可以看出,指针所指向的值发生了更改,而原变量的值未发生任何变化:
取地址和解引用操作
在C语言中取地址操作是将变量的地址赋值给指针,而解引用操作则是通过指针访问它所指向的变量的值。这两个操作在C语言的指针使用中非常重要,它们允许我们通过指针间接地访问和操作内存中的数据。
修改一下之前的代码,通过&运算符将value的地址赋给了pointer,进行了取地址操作
#include <stdio.h> int main() { int value = 20; int *pointer; // 在指针变量中存储value的地址,即取地址操作 pointer = &value; printf("value变量的地址: %p\n", &value); printf("通过pointer指针访问value变量的值: %d\n", *pointer); return 0;
}
输出中的0x7ffdee0e6ddc
是变量value在内存中的地址,内存地址是操作系统分配给程序用于存储数据的物理或虚拟内存位置,每个程序运行时,操作系统都会为其分配一块内存空间,程序中的变量就存储在这块空间的特定地址上,同时每次程序运行时,操作系统可能会分配不同的内存地址给程序中的变量,0x7ffdee0e6ddc
这个地址只是在这次运行程序时有效,下次运行时可能会有所不同。
指针的算术运算
指针的算术运算分为指针加减运算和指针相减运算
指针加减运算
:指针可以进行加减运算,其结果是指针向前或向后移动若干个元素的距离(不是字节),移动的字节数取决于指针指向的数据类型。
指针相减运算
:两个指针相减的结果是两个指针之间相隔的元素个数,要求两个指针指向同一块内存区域。
以下代码定义了两个数组:一个short
类型的dataset
数组和一个double
类型的bills
数组,每个数组都有SIZE 4
个元素。然后,它定义了两个指针变量pti
和ptf
,分别指向这两个数组的起始位置,随后代码进入一个循环,遍历这两个数组。在每次迭代中,它都会计算并打印出pti
和ptf
指针在加上index
值后的地址。这里pti + index
和ptf + index
分别表示pti
和ptf
指针向前移动index
个short
或double
元素的位置。由于指针的加减运算是以它指向的数据类型的大小为单位进行的,所以pti
每次增加2个字节(因为short类型通常占2个字节),而ptf
每次增加8个字节(因为double类型通常占8个字节)。在打印指针地址时,代码将指针转换为void*
类型。这是因为printf
函数使用%p
格式说明符来打印指针,而%p
期望一个void*
类型的参数。将指针转换为void*
类型可以确保无论指针指向什么类型的数据,都能以统一的方式打印其地址。
#include <stdio.h> #define SIZE 4 int main()
{ short dataset[SIZE]; short *pti; short index; double bills[SIZE]; double *ptf; pti = dataset; ptf = bills; printf("%23s %15s\n", "short pointers", "double pointers"); for (index = 0; index < SIZE; index++) { printf("pointers + %d: %10p %10p\n", index, (void*)(pti + index), (void*)(ptf + index)); } return 0;
}
代码会在终端输出以下内容:
short double
pointers + 0: 0x7ffc8b926ef8 0x7ffc8b926f00
pointers + 1: 0x7ffc8b926efc 0x7ffc8b926f10
pointers + 2: 0x7ffc8b926f00 0x7ffc8b926f20
pointers + 3: 0x7ffc8b926f04 0x7ffc8b926f30
指针的比较
在C语言中可以使用关系运算符(如==、<、>等)来比较两个指针,比较的是它们所指向的地址的大小。
这里定义了一个长度为10的数组array
,随后声明了三个指针ptr1、ptr2、ptr3
,ptr1
指向array
的第3个元素 ,ptr2
指向array
的第6个元素 ,ptr3
与ptr1
指向相同的地址:
#include <stdio.h> int main() { int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *ptr1, *ptr2, *ptr3; ptr1 = &array[2]; // ptr1指向array的第3个元素 ptr2 = &array[5]; // ptr2指向array的第6个元素 ptr3 = ptr1; // ptr3与ptr1指向相同的地址 // 比较ptr1和ptr2 if (ptr1 < ptr2) { printf("ptr1 < ptr2\n"); } else { printf("ptr1 >= ptr2\n"); } // 比较ptr1和ptr3 if (ptr1 == ptr3) { printf("ptr1 == ptr3\n"); } else { printf("ptr1 != ptr3\n"); } // 比较ptr2和ptr1 if (ptr2 > ptr1) { printf("ptr2 > ptr1\n"); } else { printf("ptr2 <= ptr1\n"); } return 0;
}
代码运行后再终端输出:
ptr1 < ptr2
ptr1 == ptr3
ptr2 > ptr1
指针与数组
在C语言中,数组名可以视为指向数组首元素的指针,因此可以使用指针来遍历数组元素,可以使用指针算术运算来访问数组中的元素。
以下代码中的dqys
不仅代表了一个包含12个整数的数组,同时也可以被看作是一个指向int
类型的指针,它指向dqys
数组的第一个元素,在随后的for
循环中使用了指针算术运算来遍历数组dqys
。表达式dqys + index
表示指针dqys
向前移动index
个int
类型元素的位置。因为dqys
是一个指向int的指针,所以每次递增都会使指针地址增加sizeof(int)
个字节。通过解引用操作符*
可以获取该地址处的值,即数组dqys
中索引为index
的元素的值。当index
为0时,*(dqys + 0)
就相当于dqys[0]
,它表示数组的第一个元素,其值为31。同理,*(dqys + 1)
则相当于dqys[1]
,它表示数组的第二个元素,其值为28:
#include <stdio.h>#define MONTHS 12int main()
{int dqys[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int index;for (index = 0; index < MONTHS; index++){printf("%2d 月有 %d 天. \n", index + 1, *(dqys + index));}return 0;
}
代码输出内容如下:
1 月有 31 天. 2 月有 28 天. 3 月有 31 天. 4 月有 30 天. 5 月有 31 天. 6 月有 30 天. 7 月有 31 天. 8 月有 31 天. 9 月有 30 天.
10 月有 31 天.
11 月有 30 天.
12 月有 31 天.
指针与函数
C语言中允许指针指向函数,这使得可以将函数作为参数传递给其他函数,或者通过指针调用函数,同时也可以通过将指针作为函数参数传递,可以在函数内部修改外部变量的值。
这里的modifyValue
函数接收一个int
类型的指针作为参数,并通过该指针修改外部变量的值。add
函数用来返回两个整数的和。executeFunction
函数接收一个函数指针作为参数,并通过该函数指针调用函数。随后在main
函数中,首先使用modifyValue
函数通过指针修改外部变量的值。然后声明了一个函数指针functionPointer
,并将其指向add
函数。最后将该函数指针作为参数传递给executeFunction
函数,并通过该函数指针调用add
函数。
#include <stdio.h> // 定义一个函数,该函数接收一个int类型的指针作为参数
void modifyValue(int *value) { //函数接收一个int类型的指针作为参数*value = 10; // 通过指针修改外部变量的值
} int add(int a, int b) { // 函数用来返回两个整数的和 return a + b;
} void executeFunction(int (*func)(int, int), int a, int b) { // 接收一个函数指针作为参数,并调用该函数int result = func(a, b); // 通过函数指针调用函数 printf("结果为: %d\n", result);
} int main() { int variable = 0; printf("数值修改后: %d\n", variable); modifyValue(&variable); // 将变量的地址传递给函数以修改其值 printf("数值修改前: %d\n", variable); // 使用函数指针调用函数 int (*functionPointer)(int, int) = add; // 声明一个函数指针,并将其指向add函数 executeFunction(functionPointer, 5, 3); // 将函数指针作为参数传递给executeFunction函数 return 0;
}
动态内存分配
在C语言中提供了malloc
和free
函数用于动态内存分配和释放。malloc
函数用于分配指定大小的内存空间,并返回一个指向该空间的指针;free
函数用于释放已分配的内存空间。
下面代码中先声明了一个int
类型的指针ptr
,并将其初始化为NULL
。然后使用malloc
函数动态分配了足够存储5个整数的内存空间,并将返回的指针赋值给ptr
。随后使用指针算术运算访问和修改动态分配的内存空间中的内容。之后使用for
循环打印出动态分配的内存空间中的内容。最后使用free
函数释放了已分配的内存空间,并将ptr
重新设置为NULL
避免空指针。
#include <stdio.h>
#include <stdlib.h> int main() { int *ptr = NULL; // 声明一个int类型的指针,并初始化为NULL int n = 5; // 存储5个整数 ptr = (int*)malloc(n * sizeof(int)); // 使用malloc函数动态分配内存空间,分配了n个int大小的内存空间,并将返回的指针转换为int类型的指针 // 检查malloc函数是否成功分配了内存,如果内存分配失败就退出程序 if (ptr == NULL) { printf("内存分配GG了\n"); return 1; } // 使用指针访问和修改动态分配的内存空间中的内容,通过指针算术运算访问数组元素,并赋值 for (int i = 0; i < n; i++) { *(ptr + i) = i + 1; } // 通过指针算术运算访问数组元素,并打印动态分配的内存空间中的内容 for (int i = 0; i < n; i++) { printf("%d ", *(ptr + i)); } printf("\n"); // 使用free函数释放ptr指向的内存空间 free(ptr); ptr = NULL; // 释放内存后,ptr变成了悬空指针,建议将ptr重新设置为NULL以避免悬空指针的问题 return 0;
}
多级指针、指针数组、const指针、void指针
在C语言中出了基础的指针,以下的几种指针方式也很常见
多级指针:指向指针的指针,用于实现更复杂的数据结构和操作,如动态内存分配中的二维数组。
指针数组:数组中的元素是指针类型,常用于存储多个字符串或指向函数的指针。
const指针:指向常量的指针或指针常量,用于限制指针的指向或指针所指向的值不可修改。
void指针:通用指针类型,可以指向任意类型的数据,但在使用前通常需要类型转换。
#include <stdio.h>
#include <stdlib.h> int main() { // 多级指针,指向指针的指针 int value = 10; int *ptr1 = &value; int **ptr2 = &ptr1; int ***ptr3 = &ptr2; int ****ptr4 = &ptr3;int *****ptr5 = &ptr4;int ******ptr6 = &ptr5;int *******ptr7 = &ptr6;printf("通过多级指针访问值:%d\n", *******ptr7); // 存储字符串 char *strings[] = {"Hello", "Gayboy", "GGBond"}; int i; for (i = 0; i < 3; i++) { printf("指针数组中的字符串:%s\n", strings[i]); } const int constValue = 20; const int *constPtr = &constValue; // *constPtr = 30; 不能修改指向常量的指针所指向的值 printf("指向常量的指针:%d\n", *constPtr); // 指针常量int anotherValue = 30; int *const constPtr2 = &anotherValue; // constPtr2 = &value; 这样就是错误的,指针常量的值不可修改 *constPtr2 = 40; printf("指针常量的指向值:%d\n", *constPtr2); // 最后设一个通用指针 int intValue = 50; float floatValue = 3.14f; void *voidPtr; voidPtr = &intValue; printf("通过void指针访问int值:%d\n", *(int *)voidPtr); voidPtr = &floatValue; printf("通过void指针访问float值:%f\n", *(float *)voidPtr); return 0;
}
运行结果如下:
相关文章:

C语言中的基础指针操作
在C语言中,指针是一个非常重要的概念,它提供了直接访问内存地址的能力。指针变量用于存储内存地址,而不是数据值,在某种意义上和门牌号具有相似含义:指针是一个变量,其存储的是另一个变量的内存地址&#x…...

LabVIEW环境下OCR文字识别的实现策略与挑战解析
引言 在自动化测试领域,OCR(Optical Character Recognition,光学字符识别)技术扮演着重要角色,它能够将图像中的文字转换成机器可编辑的格式。对于使用LabVIEW约5个月,主要进行仪器控制与数据采集的你而言…...

中英双语介绍美国的州:堪萨斯州(Kansas)
中文版 堪萨斯州简介 堪萨斯州(Kansas)位于美国中部,以其广阔的平原、丰富的农业资源和多样的文化遗产而著称。以下是对堪萨斯州的详细介绍,包括其地理位置、人口、经济、教育、文化和主要城市。 地理位置 堪萨斯州位于美国中…...

信息收集---端口服务信息收集
1. 什么是端口 是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟端口指计算机内部或交换机路由器内的端口,不可见。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口,是可见端口,计算机背板的RJ45…...

Python知识点背诵手册,超详细知识梳理
一、手册介绍 《Python知识点背诵手册》是一份详尽的Python学习资料,旨在帮助学习者系统地掌握Python语言的基础知识和进阶技能。该手册将Python的所有关键语法和概念进行了精炼的总结,并以易于理解和记忆的方式呈现。以下是手册的主要特点和内容概述&a…...

【Pytorch实用教程】如何在多个GPU上使用分布式数据并行进行训练模型
文章目录 1. 代码(可直接运行,含随机生成的训练数据)2. 代码的详细解释2.1. 导入必要的库和模块2.2. 设置每个进程的初始设置2.3. 随机生成数据集类 `RandomDataset`2.4. 训练函数 `train`1. 代码(可直接运行,含随机生成的训练数据) 以下是一个基于PyTorch的多GPU分布式…...

PIL,OpenCV,Pytorch处理图像时的通道顺序(颜色,长宽深)
项目颜色通道顺序长宽通道顺序数据类型取值范围PILRGBHWCndarray0-255 (byte)OpenCVBGRHWCndarray0-255 (byte)PyTorchRGB/BGR (取决于如何读取)(N)CHWtensor0-1 (float, 标准化后); 0-255 (int, 未标准化) 注意以下几点: 颜色通道顺序:PIL默认使用RGB顺…...

经纬恒润亮相2024世界智能产业博览会
近日,以“智行天下 能动未来”为主题的2024世界智能产业博览会(以下简称“智博会”)在国家会展中心(天津)成功举办。本次智博会上,经纬恒润自主研发的汽车电子产品联合天津(西青)国家…...

Python序列化和反序列化
一.序列化和反序列化 在Python中,序列化(Serialization)和反序列化(Deserialization)是处理对象数据的过程,主要用于对象的存储或网络传输。 序列化(Serialization) 序列化是将Pyth…...

Stream toArray 好过collect
toArray 比collect 更好用,这样就不需要判断Null。 if(_user.getUserRole()!null) {_user.setRole(_roleList.stream().filter(_e->_e.getRoleId()_user.getUserRole()).toArray(Role[]::new)[0]); } if(_user.getUserRole()!null) {_user.setRole(_roleList.s…...

qt/c++/mysql教务管理系统
简介 qt/c/mysql教务管理系统 学生端,教师端,管理员端 演示 qt/c/mysql教务管理系统 源码获取 printf("白嫖勿扰,需要的加v%s","ywj17347418171");...

Echarts公共方法
Vue引入Echarts install 1.安装Echartsnpm install echarts --save 2.项目全局引入形式--#main.js 全局引入形式import * as echarts from "echarts"Vue.prototype.$echarts echarts 公共方法JS /*** author: wangjie* description: 通用echarts图表封装* date: …...

鸿蒙学习(二)
文章目录 1、弹窗2、走马灯(实现轮播图效果)3、注解6、多选框、单选框7、Stack8、TextTimer9、DatePicker 1、弹窗 显示提示信息或者用于用户交互 导入模块 prompt 接口 showToast:显示toast showDialog:显示对话框 showContextMenu:显示上下文菜单 sh…...

企业机构营销目前106短信群发还有用吗?此文告诉你该如何抉择!
在当今竞争激烈的企业营销环境中,106短信群发平台依然是众多企业机构青睐的营销工具之一。尽管互联网技术的发展带来了多样化的沟通方式,但106短信群发凭借其直达性强、成本低廉、覆盖广泛等优势,仍然保持着不错的营销效果。然而,…...

DJYGUI AI低代码图形编程开发平台:开启嵌入式软件图形编程新纪元
在科技高速发展的当今时代,软件开发行业对创新和高效的需求日益增长。DJYGUI AI低代码图形编程开发平台的出现,为智能屏、物联屏、串口屏等嵌入式显示设备领域带来了全新的机遇。该平台由都江堰操作系统 AI 代码自动生成平台研发,具有显著的优…...

为什么不能在foreach中删除元素
文章目录 快速失败机制(fail-fast)for-each删除元素为什么报错原因分析逻辑分析 如何正确的删除元素remove 后 breakfor 循环使用 Iterator 总结 快速失败机制(fail-fast) In systems design, a fail-fast system is one which i…...

python学习-tuple及str
为什么需要元组 定义元组 元组的相关操作 元组的相关操作 - 注意事项 元组的特点 字符串 字符串的下标(索引) 同元组一样,字符串是一个:无法修改的数据容器。 如果必须要修改字符串,只能得到一个新的字符串ÿ…...

Python深度理解系列之【排序算法——冒泡排序】
读者大大们好呀!!!☀️☀️☀️ 👀期待大大的关注哦❗️❗️❗️ 🚀欢迎收看我的主页文章➡️木道寻的主页 文章目录 🔥前言🚀冒泡排序python实现算法实现图形化算法展示 ⭐️⭐️⭐️总结 🔥前…...

边界框在目标检测中的作用与应用
目标检测是计算机视觉领域的核心任务之一,旨在从图像或视频中识别和定位感兴趣的目标。边界框(Bounding Box)是目标检测中常用的一种表示方法,用于确定目标在图像中的确切位置。本文将详细探讨边界框的概念、它在目标检测中的角色…...

linux 环境报错:Peer reports incompatible or unsupported protocol version
出现问题的原因: curl 不兼容或不支持的协议版本。 解决方案: yum update -y nss curl libcurl如此继续之前的操作即可。...

深入解析:Java爬虫的本质是什么?
深入解析:Java爬虫的本质是什么? 引言: 随着互联网的快速发展,获取网络数据已成为许多应用场景中的重要需求。而爬虫作为一种自动化程序,能够模拟人类浏览器的行为,从网页中提取所需信息,成为了…...

【Matlab 六自由度机器人】机器人动力学之推导拉格朗日方程(附MATLAB机器人动力学拉格朗日方程推导代码)
【Matlab 六自由度机器人】机器人动力学概述 近期更新前言正文一、拉格朗日方程的推导1. 单自由度系统2. 单连杆机械臂系统3. 双连杆机械臂系统 二、MATLAB实例推导1. 机器人模型的建立2. 动力学代码 总结参考文献 近期更新 【汇总】 【Matlab 六自由度机器人】系列文章汇总 …...

线下生鲜蔬果店做小程序有什么方法
生鲜蔬果是生活所需,大小商家众多,零售批发各种经营模式,小摊贩或是超市门店都有着目标客户或准属性群体。竞争和获客转化也促进着商家寻找客源和加快线上进程。 尤其是以微信社交为主的私域场景,普客/会员都需要精细化管理营收和…...

几种linux开机自启脚本的方法
几种linux开机自启脚本的方法 1. 脚本添加到init.d目录中2. 创建服务service(推荐)3. /etc/profile & /etc/profile.d(不推荐)4. /etc/rc.local 本文以启动jenkins节点为例,需要持久连接,实现开机自启 …...

Qt开发笔记:Qt3D三维开发笔记(一):Qt3D三维开发基础概念介绍
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://blog.csdn.net/qq21497936/article/details/140059315 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、O…...

Firewalld 防火墙基础
Firewalld 防火墙基础 一、Firewalld概述firewalld 简介firewalld 和 iptables 的关系firewalld 与 iptables service 的区别 二、Firewalld 网络区域区域介绍Firewalld数据处理流程 三、Firewalld 防火墙的配置方法firewall-config 图形工具“区域”选项卡“服务”选项卡改变防…...

针对 Windows 10 的功能更新,版本 22H2 - 错误 0xc1900204
最近想帮女朋友生win11发现她电脑安装更新总是卡到安装%10这里失败 原来是安装路径被修改过了,改回c盘 win R → 输入regedit 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion...

goframe框架规范限制(but it should be named with “Res“ suffix like “XxxRes“)
背景: 首页公司最近要启动一个项目,公司主要业务是用java开发的,但是目前这个方向的项目,公司要求部署在主机上,就是普通的一台电脑上,电脑配置不详,还有经常开关机,所以用java面临…...

格式化选NTFS还是exFAT 格式化NTFS后Mac不能用怎么办 移动硬盘格式化ntfs和exfat的区别
面对硬盘、U盘或移动硬盘的格式化决策,NTFS与exFAT作为主流的文件系统,用户在选择时可以根据它们的不同特点来选择适用场景。下面我们来看看格式化选NTFS还是exFAT,格式化NTFS后Mac不能用怎么办的相关内容。 一、格式化选NTFS还是exFAT 在数…...

中国桥梁空间分布数据
2020年中国桥梁空间分布数据,共包含102000余条数据。 数据属性表包括:地级市名、区县名、桥梁名称和经纬度。有shp和EXCEl两种格式数据。目前暂没有广西、广东和台湾三个省份数据。...