✨博客主页 | |
---|---|
何曾参静谧的博客 | |
文章专栏 | |
「C/C++」C/C++程序设计 | |
全部专栏 | |
「UG/NX」NX二次开发 | 「UG/NX」BlockUI集合 |
「VS」Visual Studio | 「QT」QT5程序设计 |
「C/C++」C/C++程序设计 | 「Win」Windows程序设计 |
「DSA」数据结构与算法 | 「File」数据文件格式 |
指针的本质:
- 是一种特殊的变量
指针声明赋值和访问:
- 在声明指针时,需要使用
*
符号来表示该变量是一个指针变量,使用&
符号来取变量的地址。可以将变量的地址赋给指针,例如int value = 10; int* ptr = &value;
,这样ptr
将指向value
的内存地址。通过指针,可以访问变量的值,例如*ptr = 20;
可以将指针所指向的变量的值改为 20。
野指针和悬挂指针:
- 如果指针没有被初始化或者指向已经释放的内存地址,就称为野指针。悬挂指针是指指针在程序的生命周期内超出了其所指向的变量的作用域而仍然保持有效。
int* ptr = NULL;
常量与指针:
- 目的是是指针的值与地址不被更改。
数组与指针:
- 指针可以用于处理数组。通过将指针与索引结合使用,可以遍历数组元素、访问特定位置的元素,或者实现动态数组。
函数与指针:
函数参数传递:
通过指针作为函数参数,可以在函数中对传入的变量进行修改。这样可以传递复杂的数据结构,减少数据的拷贝开销。
函数返回值:
函数可以返回指针,以便在函数外部使用函数内部创建的对象。这在需要保留函数内部创建的对象并在其他地方使用时非常有用。
函数指针:
指针可以指向函数,从而允许通过指针调用特定的函数。这使得函数可以像数据一样被传递、存储和操作。
动态内存分配:
- 使用
new
运算符可以动态地在堆(heap)上分配内存,并返回指向该内存的指针。通过这种方式,可以在运行时动态地分配和释放内存,例如int* value = new int;
。使用delete
运算符来释放通过new
运算符分配的内存。这样可以确保在不再需要某段内存时将其还给系统,避免内存泄漏。
数据结构:
- 指针广泛用于实现各种数据结构,如链表、树、图等。通过指针,可以动态连接和操作多个对象。
硬件交互:
- 在一些底层编程领域,指针常用于直接访问内存地址,从而与硬件或操作系统进行交互。
//*指针声明:以下两种都可以。
数据类型 *变量名; int *a;
数据类型* 变量名; int* a;(个人采用这种,比较直观)
int number = 5;
int* ptr = &number;//指针的定义与赋值
图片素材来源:动画讲编程
int main() {
int number = 5;
int* ptr1 = &number;
int** ptr2 = &ptr1;
int*** ptr3 = &ptr2;
}
指针运算是对指针进行数学运算,以在内存中导航和访问数据。指针运算包括指针的加法、减法和比较操作。
当一个指针与一个整数相加时,这个整数会乘以指针所指向类型的大小(以字节为单位),然后将结果加到指针中。例如:
int* ptr = somePointer;
ptr = ptr + 3; // 将指针向后移动 3 个 int 的大小
在上述例子中,
ptr
指针向后移动了 3 个 int 大小的距离。
当两个指针相减时,它们之间的距离(以数组元素或对象的个数为单位)将被计算,并返回结果作为整数。例如:
int* ptr1 = somePointer1;
int* ptr2 = somePointer2;
int diff = ptr1 - ptr2; // 计算两个指针之间的距离
在上述例子中,
diff
将包含ptr1
和ptr2
之间的元素或对象个数。
可以使用递增或递减运算符直接修改指针的值,使其指向下一个或上一个元素。例如:
int* ptr = somePointer;
ptr++; // 将指针移动到下一个 int
在上述例子中,
ptr
指针被递增,使其指向下一个 int。
可以通过比较两个指针的大小关系来确定它们是否指向相同的内存区域或者在内存中的位置先后关系。例如:
int* ptr1 = somePointer1;
int* ptr2 = somePointer2;
if (ptr1 < ptr2) {
// ptr1 在 ptr2 之前
} else if (ptr1 > ptr2) {
// ptr1 在 ptr2 之后
} else {
// ptr1 和 ptr2 指向相同的内存位置
}
在上述例子中,我们可以根据指针之间的比较结果执行相应的操作。
需要注意的是,指针运算必须在合法的内存地址范围内进行。还要注意,在进行指针运算时,一定要确保不会发生指针指向未分配的内存或者超出可访问范围的情况。
口诀:
左数右指(const在左边时数据为常量,const在右边时指针为常量)
常量指针:
地址可变,指向数据不可变
const int* p;
int const* p;
#include
int main(){
int i = 0;
const int* p1 = &i;
int const* p2 = &i;
*p1 = 1; // compile error
p1 = NULL; // ok
*p2 = 2; // compile error
p2 = NULL; // ok
return 0;
}
指针常量:
地址不可变,指向数据可变
int* const p;
#include
int main(){
int i = 0;
int* const p3 = &i;
*p3 = 3; // ok
p3 = NULL; // compile error
return 0;
}
常量指针常量:
地址不可变,指向数据不可变
const int* const p;
#include
int main(){
int i = 0;
const int* const p4 = &i;
*p4 = 4; // compile error
p4 = NULL; // compile error
return 0;
}
数组的类型是由元素类型和数组大小共同决定的。
数组声明 | 数组类型 |
---|---|
int array[5] | int[5] |
int matrix[3][3] | int[3][3] |
//格式:
typedef type(name)[size];
//数组类型:
typedef int(AINT5)[5];
typedef doubel(ADOUBLE)[10];
//数组定义:
AINT5 iArray;
ADOUBLE dArray;
数组指针是一个指针,指向对应类型的数组。
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr;
一维数组指针
是指向一维数组的指针变量。它可以用于访问和操作一维数组中的元素。在C++中,可以使用指针来表示一维数组指针。
#include
int main() {
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr; // 声明一个指向整型的指针,并指向数组的第一个元素
// 访问一维数组中的元素
std::cout << *ptr << std::endl; // 输出:1
std::cout << *(ptr + 2) << std::endl; // 输出:3
// 修改一维数组中的元素
*ptr = 100;
*(ptr + 2) = 300;
// 打印修改后的值
std::cout << *ptr << std::endl; // 输出:100
std::cout << *(ptr + 2) << std::endl; // 输出:300
return 0;
}
在上述示例中,我们首先定义并初始化了一个整数类型的一维数组
int arr[]
。然后,我们声明了一个指向整数的指针int* ptr
。
接下来,我们将指针ptr
指向数组的第一个元素(数组名即为指向第一个元素的指针),这样我们就可以使用ptr
来访问和修改一维数组中的元素了。
使用一维数组指针
时,可以使用解引用操作符*
来访问指针指向的元素值,可以使用指针算术运算(如+
)来访问其他索引处的元素。例如,ptr + 2
将指针移动两个位置,然后通过解引用操作符来访问相应的元素。
二维数组指针
是指向二维数组的指针变量。它可以用于访问和操作二维数组中的元素。在C++中,可以使用指针数组或双重指针来表示二维数组指针。
#include
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*ptr)[4] = NULL; // 声明一个指向包含4个整数的数组的指针
ptr = arr; // 将指针指向二维数组
// 访问二维数组中的元素
std::cout << ptr[0][0] << std::endl; // 输出:1
std::cout << ptr[1][2] << std::endl; // 输出:7
// 修改二维数组中的元素
ptr[0][0] = 100;
ptr[1][2] = 200;
// 打印修改后的值
std::cout << ptr[0][0] << std::endl; // 输出:100
std::cout << ptr[1][2] << std::endl; // 输出:200
return 0;
}
在上述示例中,我们首先声明并初始化了一个3x4的二维整数数组
int arr[3][4]
。然后,我们声明了一个指向包含4个整数的数组的指针int (*ptr)[4]
。
接下来,我们将指针ptr
指向二维数组arr
,这样我们就可以使用ptr
来访问和修改二维数组中的元素了。
使用二维数组指针时,可以使用类似于二维数组的语法来访问和修改元素,可以使用ptr[i][j]
的形式。在内存中,二维数组元素在一维地址空间中是按行连续存储的。
多维数组指针
是指向多维数组的指针变量。它可以用于访问和操作多维数组中的元素。在C++中,可以使用多级指针来表示多维数组指针。
#include
int main() {
int arr[2][3][4] = {
{{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}},
{{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}}
};
int (*ptr)[3][4] = NULL; // 声明一个指向包含3个二维数组的指针变量
ptr = arr; // 将指针指向三维数组
// 访问三维数组中的元素
std::cout << *(*(*(ptr + 1) + 2) + 3) << std::endl; // 输出:24
// 修改三维数组中的元素
*(*(*(ptr + 0) + 1) + 2) = 100;
// 打印修改后的值
std::cout << *(*(*(ptr + 0) + 1) + 2) << std::endl; // 输出:100
return 0;
}
在上述示例中,我们首先声明并初始化了一个2x3x4的三维整数数组
int arr[2][3][4]
。然后,我们声明了一个指向包含3个二维数组的指针int (*ptr)[3][4]
。
接下来,我们将指针ptr
指向三维数组arr
,这样我们就可以使用ptr
来访问和修改三维数组中的元素了。
使用三维数组指针时,可以使用多级解引用操作符(*
)来访问指针指向的元素值。例如,*(*(*(ptr + 1) + 2) + 3)
将移动指针到下一个二维数组,然后移动到下一个一维数组,最后通过解引用操作符访问该一维数组的第4个元素。
指针数组是一个数组,其中元素都是指针。
type* pArray[n];
int* pointerArray[] = {&a, &b, &c};
// 声明并初始化指针数组
//下面是一个简单的示例,展示了如何声明、初始化和使用指针数组:
#include
int main() {
int a = 10;
int b = 20;
int c = 30;
int* pointerArray[] = {&a, &b, &c}; // 声明并初始化指针数组
// 访问指针数组的元素并打印对应值
std::cout << *pointerArray[0] << std::endl; // 输出:10
std::cout << *pointerArray[1] << std::endl; // 输出:20
std::cout << *pointerArray[2] << std::endl; // 输出:30
// 修改指针数组元素的值
*pointerArray[0] = 100;
*pointerArray[1] = 200;
*pointerArray[2] = 300;
// 打印修改后的值
std::cout << *pointerArray[0] << std::endl; // 输出:100
std::cout << *pointerArray[1] << std::endl; // 输出:200
std::cout << *pointerArray[2] << std::endl; // 输出:300
return 0;
}
在上述示例中,通过定义
int* pointerArray[]
来声明一个指针数组。使用花括号初始化,将变量a
、b
和c
的地址作为指针数组的元素进行存储。
然后,我们通过解引用操作符*
来访问指针数组中的元素,并输出对应的值。可以直接修改指针数组元素指向的值,通过解引用操作符*
修改对应内存地址上的值。最后,打印修改后的值进行验证。
函数的本质是一段内存中的代码(占用一片连续内存)。
函数名就是函数体代码的起始地址(函数入口地址)。
- 如
Type(*pFunc)(Type1,Type2) = func;
与Type(*pFunc)(Type1,Type2) = &func;
两者等价。
函数声明 | 函数类型 |
---|---|
int sum(int a, int b) | int (int,int) |
void swap(int* a,int* b) | void (int*,int*) |
void fun(void) | void (void) |
#include
// 原始的函数类型声明
typedef int (*MathFunction)(int, int);
// 定义一个函数类型为 MathFunction 的函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
// 声明函数指针并初始化
MathFunction mathFuncPtr = nullptr;
// 指向 add 函数
mathFuncPtr = add;
std::cout << "add(3, 2) = " << mathFuncPtr(3, 2) << std::endl;
// 指向 subtract 函数
mathFuncPtr = subtract;
std::cout << "subtract(3, 2) = " << mathFuncPtr(3, 2) << std::endl;
return 0;
}
函数参数传递为指针
是一种常见的方式,用于在函数中对变量进行修改或者引用变量的原始值。通过将指针作为参数传递给函数,函数可以直接访问和修改指针所指向的内存地址上的数据。
#include
// 通过指针修改变量值
void incrementByPointer(int* num) {
// 使用解引用操作符(*)修改指针所指向的内存中的值
(*num)++;
}
// 通过指针交换两个变量的值
void swapByPointer(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int num = 10;
int a = 5, b = 3;
std::cout << "初始值:num = " << num << std::endl;
incrementByPointer(&num);
std::cout << "调用incrementByPointer后的值:num = " << num << std::endl;
std::cout << "初始值:a = " << a << ", b = " << b << std::endl;
swapByPointer(&a, &b);
std::cout << "调用swapByPointer后的值:a = " << a << ", b = " << b << std::endl;
return 0;
}
在上述代码中,我们定义了两个函数:
incrementByPointer
和swapByPointer
。这两个函数都接受指针作为参数,以实现对变量的修改。
在incrementByPointer
中,我们通过解引用操作符*
来访问并修改指针所指向内存中的值。
在swapByPointer
中,我们使用指针来交换两个变量的值。通过解引用操作符*
,我们可以访问指针所指向内存中的值,并进行交换操作。
在主函数中,我们声明了一个变量num
,并调用incrementByPointer
函数来增加其值。同样地,我们声明了两个变量a
和b
,并调用swapByPointer
函数来交换它们的值。
函数返回值为指针
是一种常见的方式,用于从函数中返回动态分配的内存或者返回指向其他数据结构的指针。通过将函数的返回类型声明为指针,函数可以返回一个指向特定类型数据的内存地址。
#include
// 返回动态分配内存的指针
int* createArray(int size) {
int* arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
return arr;
}
// 返回指向全局变量的指针
int* getGlobalVariable() {
static int globalVar = 10;
return &globalVar;
}
int main() {
// 调用函数返回动态分配内存的指针
int* dynamicArray = createArray(5);
for (int i = 0; i < 5; i++) {
std::cout << dynamicArray[i] << " ";
}
std::cout << std::endl;
delete[] dynamicArray; // 释放动态分配的内存
// 调用函数返回指向全局变量的指针
int* pointerToGlobalVar = getGlobalVariable();
std::cout << "全局变量的值:" << *pointerToGlobalVar << std::endl;
return 0;
}
在上述代码中,我们定义了两个函数:
createArray
和getGlobalVariable
。这两个函数都返回指针类型的值。
在createArray
函数中,我们使用new
操作符动态分配一个整型数组,并为其赋初值。然后,我们返回这个动态分配内存的指针。
在getGlobalVariable
函数中,我们声明了一个静态局部变量globalVar
,并将其地址返回。
在主函数中,我们调用createArray
函数创建一个包含一些数字的整型数组,并打印数组中的每个元素。注意,我们在使用完动态分配的内存后,使用delete[]
释放了这块内存。
接着,我们调用getGlobalVariable
函数获取全局变量的值,并通过指针访问该变量的值。
函数指针
是指向函数的指针变量。它可以用于调用函数,实现函数的动态绑定和回调等功能。函数指针的声明和使用方式与其他指针类型类似。
#include
// 声明一个函数指针类型,该函数接受两个整数参数并返回一个整数
typedef int (*ArithmeticFunction)(int, int);
// 定义两个用于演示的加法和乘法函数
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
// 声明一个函数指针变量,并将其指向add函数
ArithmeticFunction ptr = add;
// 使用函数指针调用函数
int result = ptr(2, 3); // 等同于调用add(2, 3)
std::cout << result << std::endl; // 输出:5
// 将函数指针指向multiply函数
ptr = multiply;
// 使用函数指针调用函数
result = ptr(2, 3); // 等同于调用multiply(2, 3)
std::cout << result << std::endl; // 输出:6
return 0;
}
在上述示例中,我们首先通过
typedef
关键字定义了一个名为ArithmeticFunction
的函数指针类型。该函数指针类型接受两个整数参数并返回一个整数。
接下来,我们定义了add
和multiply
两个函数,用于演示函数指针的用法。
在main
函数中,我们声明了一个函数指针变量ptr
,并将其初始化为add
函数。然后,使用函数指针变量调用add
函数,将结果输出。
接着,我们将函数指针变量ptr
指向multiply
函数,并再次使用函数指针调用multiply
函数,将结果输出。
函数指针可以在运行时动态地指向不同的函数,可以将函数指针作为参数传递给其他函数,实现回调功能或根据条件选择不同的函数执行逻辑等。
通过函数指针作为参数,使相同的代码实现不同功能
#include
int add(int a, int b){
return a + b;
}
int mul(int a, int b){
return a * b;
}
int calculate(int a[], int len, int(*cal)(int, int)){
int ret = a[0];
for(int i=1; i<len; i++){
ret = cal(ret, a[i]);
}
return ret;
}
int main(){
int a[5] = {1,2,3,4,5};
std::cout << "1 + ... + 5 = " << calculate(a, 5, add) << std::endl; // 输出:15
std::cout << "1 * ... * 5 = " << calculate(a, 5, mul) << std::endl; // 输出:120
return 0;
}
堆空间是程序中预留且可用的内存区域
c语言库:
#include
申请内存:void* malloc(unsigned bytes)
(判断是否申请成功)
释放内存:void free(void* p)
(不可多次释放)
//int* arr = (int*)malloc(size * sizeof(int)); // 动态分配内存
//free(arr); // 释放动态分配的内存
#include
#include
int main() {
int size = 5;
int* arr = (int*)malloc(size * sizeof(int)); // 动态分配内存
if (arr != NULL) {
// 使用动态分配的内存
int i = 0;
for (i = 0; i < size; i++) {
arr[i] = i + 1;
printf("%d ", arr[i]);
}
printf("\n");
free(arr); // 释放动态分配的内存
}
return 0;
}
C++使用
new
和delete
进行内存动态分配和释放
int* p = new int(10);
delete[] p;
数据结构是一种组织和存储数据的方式,它定义了数据元素之间的关系以及对这些数据元素进行操作的规则。指针是一种特殊的数据类型,用于存储变量的内存地址。通过使用指针,我们可以有效地在数据结构中操作和访问数据。
#include
// 定义一个简单的链表结构
struct Node {
int data;
Node* next;
};
// 在链表末尾插入新节点
void insertNode(Node* &head, int value) {
// 创建新节点
Node* newNode = new Node;
newNode->data = value;
newNode->next = nullptr;
if (head == nullptr) {
// 链表为空,将新节点设为头节点
head = newNode;
} else {
// 找到链表末尾,并将新节点插入
Node* temp = head;
while (temp->next != nullptr) {
temp = temp->next;
}
temp->next = newNode;
}
}
// 遍历并打印链表中的每个节点
void printLinkedList(Node* head) {
Node* temp = head;
while (temp != nullptr) {
std::cout << temp->data << " ";
temp = temp->next;
}
std::cout << std::endl;
}
// 释放链表占用的内存
void deleteLinkedList(Node* &head) {
Node* temp = head;
while (temp != nullptr) {
Node* nextNode = temp->next;
delete temp;
temp = nextNode;
}
head = nullptr;
}
int main() {
// 创建一个空链表
Node* head = nullptr;
// 向链表中插入节点
insertNode(head, 1);
insertNode(head, 2);
insertNode(head, 3);
// 打印链表中的节点
printLinkedList(head);
// 删除链表,释放内存
deleteLinkedList(head);
return 0;
}
在上述代码中,我们定义了一个简单的链表结构
Node
,其中的next
成员是一个指向下一个节点的指针。
通过使用指针,我们可以在insertNode
函数中创建新的节点,并将其插入链表的末尾。在printLinkedList
函数中,我们遍历链表中的每个节点,并打印出节点的值。
主函数中,我们首先创建一个空链表,并通过调用insertNode
函数向链表中插入节点。最后,我们调用printLinkedList
函数来打印链表中的节点值。
类指针
是指向类对象的指针变量。使用类指针可以操作、访问和传递类对象,以实现动态创建和管理类实例的功能。
#include
#include
// 定义一个简单的Person类
class Person {
public:
std::string name;
int age;
void introduce() {
std::cout << "My name is " << name
<< " and I am " << age << " years old." << std::endl;
}
};
int main() {
// 声明一个指向Person类对象的指针
Person* ptr;
// 创建一个Person类对象,并将指针ptr指向该对象
Person person;
person.name = "Alice";
person.age = 25;
// 指向person类
ptr = &person;
// 通过类指针访问和操作类对象
ptr->introduce();
// 修改类对象的属性
ptr->age = 30;
// 再次通过类指针访问和操作类对象
ptr->introduce();
return 0;
}
在上述示例中,我们定义了一个简单的
Person
类,该类具有name
和age
属性,以及一个introduce
成员函数用于打印个人信息。
在main
函数中,我们首先声明了一个指向Person
类对象的指针ptr
。然后,我们创建了一个名为person
的Person
类对象,并通过指针ptr
将其地址赋值给指针。
通过类指针ptr
,我们可以访问和操作类对象的成员。例如,调用ptr->introduce()
函数打印类对象的信息。
我们还可以通过类指针修改类对象的属性,如ptr->age = 30;
。
类指针在实际应用中非常有用,它允许动态创建和管理类对象。通过类指针,可以在运行时创建和销毁对象,以及在不同的函数之间共享和传递对象。
void*
指针只能保存地址,不能获取数据。
void*
可以与其他数据指针互相赋值。
int a = 5;
void* p = &a;
右左法则(Right-Left Rule)
是一种用于解析复杂的C或C++声明的技巧。它帮助程序员理解声明中各个部分的含义。
右左法则的规则如下:
- 从变量名或标识符开始,从右向左阅读声明。
- 遇到括号时,首先解析括号内的内容。
- 解析指针(*)和数组([])符号,注意左结合性。例如,
int* a[]
应该解析为“a是一个数组,其中元素是指向整数的指针”。- 解析函数参数列表及返回类型,注意左结合性。例如,
int (*func)(int)
应该解析为“func是一个指针,指向一个函数,该函数接受int类型的参数并返回一个int类型的值”。
int* (*funcPtr)(char*, double);
// 使用右左法则解析上述声明:
// funcPtr 是一个指针,指向一个函数
// 该函数接受一个char*类型的参数和一个double类型的参数
// 并返回一个指向整数的指针
在上述示例中,我们有一个复杂的声明
int* (*funcPtr)(char*, double)
。根据右左法则,我们可以逐步解析该声明。
- 从变量名
funcPtr
开始,它是一个指针。- 指针指向一个函数,使用括号括起来
(char*, double)
。- 函数接受一个
char*
类型的参数和一个double
类型的参数。- 函数返回一个指向整数的指针
int*
。
int (*p1)(int*,int (*f)(int*));
int (*p2[5])(int*);
int (*(*p3)[5])(int*);
int*(*(*p4)(int*))(int*);
int (*(*p5)(int*))[5];
检查空指针:
- 在使用指针之前,应该始终进行空指针检查,确保指针有有效的内存地址。可以使用条件判断语句,比如
if (pointer != NULL)
来检查指针是否为空。
避免野指针:
- 避免使用未初始化或已释放的指针,这被称为野指针。使用野指针可能会导致程序崩溃或产生不可预测的结果。在使用指针之前,应该确保其已经正确初始化,并且在不需要使用指针时及时释放内存。
delete p;
p = NULL:
防止内存泄漏:
- 在动态分配内存时,要记得在不再需要使用内存时释放它,以避免内存泄漏。内存泄漏会导致程序占用越来越多的内存,最终可能导致系统性能下降或崩溃。使用
free()
函数(C语言)或delete
运算符(C++)来释放动态分配的内存。
确保类型匹配:
- 指针与所指向的对象或变量的类型必须匹配。例如,一个整型指针应该指向整型变量,而不是字符或其他类型的变量。使用不匹配类型的指针可能会导致内存访问错误或数据损坏。
避免越界访问:
- 指针通过存储变量的内存地址来访问其值。在使用指针时,应该确保不对超出指针范围的内存进行访问。越界访问可能会导致程序错误、数据损坏和安全漏洞。
指针赋值问题:
- 不同类型的指针是不能够进行赋值的。(
void*
指针可以与其他数据指针相互赋值,但不可以直接获取内存数据)