「C/C++」C/C++指针详解
✨博客主页 | |
---|---|
何曾参静谧的博客 | |
📌文章专栏 | |
「C/C++」C/C++程序设计 | |
📚全部专栏 | |
「UG/NX」NX二次开发 | 「UG/NX」BlockUI集合 |
「VS」Visual Studio | 「QT」QT5程序设计 |
「C/C++」C/C++程序设计 | 「Win」Windows程序设计 |
「算法」数据结构与算法 | 「File」数据文件格式 |
目录
- 一、术语介绍
- 二、指针声明与赋值
- 2.1、指针声明
- 2.2、指针赋值
- 三、多级指针
- 四、指针运算
- 4.1、指针的加法运算
- 4.2、 指针的减法运算
- 4.3、 指针的递增和递减运算
- 4.4、 比较运算
- 五、常量与指针
- 5.1、常量指针
- 5.2、指针常量
- 5.3、常量指针常量
- 六、数组与指针
- 6.1、重命名数组类型
- 6.2、数组指针
- 6.2.1、一维数组指针
- 6.2.2、二维数组指针
- 6.2.3、多维数组指针
- 6.3、指针数组
- 七、函数与指针
- 7.1、重命名函数类型
- 7.2、函数参数传递为指针
- 7.3、函数返回值为指针
- 7.4、函数指针
- 7.5、回调函数应用
- 八、堆空间与指针
- 8.1、动态分配内存
- 九、数据结构与指针
- 十、类与指针
- 十一、void* 指针
- 十二、指针阅读技巧
- 十三、注意事项
一、术语介绍
指针的本质:
- 是一种特殊的变量
指针声明赋值和访问:
- 在声明指针时,需要使用
*
符号来表示该变量是一个指针变量,使用&
符号来取变量的地址。可以将变量的地址赋给指针,例如int value = 10; int* ptr = &value;
,这样ptr
将指向value
的内存地址。通过指针,可以访问变量的值,例如*ptr = 20;
可以将指针所指向的变量的值改为 20。
野指针和悬挂指针:
- 如果指针没有被初始化或者指向已经释放的内存地址,就称为野指针。悬挂指针是指指针在程序的生命周期内超出了其所指向的变量的作用域而仍然保持有效。
int* ptr = NULL;
常量与指针:
- 目的是是指针的值与地址不被更改。
数组与指针:
- 指针可以用于处理数组。通过将指针与索引结合使用,可以遍历数组元素、访问特定位置的元素,或者实现动态数组。
函数与指针:
函数参数传递:
通过指针作为函数参数,可以在函数中对传入的变量进行修改。这样可以传递复杂的数据结构,减少数据的拷贝开销。
函数返回值:
函数可以返回指针,以便在函数外部使用函数内部创建的对象。这在需要保留函数内部创建的对象并在其他地方使用时非常有用。
函数指针:
指针可以指向函数,从而允许通过指针调用特定的函数。这使得函数可以像数据一样被传递、存储和操作。
动态内存分配:
- 使用
new
运算符可以动态地在堆(heap)上分配内存,并返回指向该内存的指针。通过这种方式,可以在运行时动态地分配和释放内存,例如int* value = new int;
。使用delete
运算符来释放通过new
运算符分配的内存。这样可以确保在不再需要某段内存时将其还给系统,避免内存泄漏。
数据结构:
- 指针广泛用于实现各种数据结构,如链表、树、图等。通过指针,可以动态连接和操作多个对象。
硬件交互:
- 在一些底层编程领域,指针常用于直接访问内存地址,从而与硬件或操作系统进行交互。
二、指针声明与赋值
2.1、指针声明
//*指针声明:以下两种都可以。
数据类型 *变量名; int *a;
数据类型* 变量名; int* a;(个人采用这种,比较直观)
2.2、指针赋值
int number = 5;
int* ptr = &number;//指针的定义与赋值
图片素材来源:动画讲编程
三、多级指针
int main() {int number = 5;int* ptr1 = &number;int** ptr2 = &ptr1;int*** ptr3 = &ptr2;
}
四、指针运算
指针运算是对指针进行数学运算,以在内存中导航和访问数据。指针运算包括指针的加法、减法和比较操作。
4.1、指针的加法运算
当一个指针与一个整数相加时,这个整数会乘以指针所指向类型的大小(以字节为单位),然后将结果加到指针中。例如:
int* ptr = somePointer;
ptr = ptr + 3; // 将指针向后移动 3 个 int 的大小
在上述例子中,
ptr
指针向后移动了 3 个 int 大小的距离。
4.2、 指针的减法运算
当两个指针相减时,它们之间的距离(以数组元素或对象的个数为单位)将被计算,并返回结果作为整数。例如:
int* ptr1 = somePointer1;
int* ptr2 = somePointer2;
int diff = ptr1 - ptr2; // 计算两个指针之间的距离
在上述例子中,
diff
将包含ptr1
和ptr2
之间的元素或对象个数。
4.3、 指针的递增和递减运算
可以使用递增或递减运算符直接修改指针的值,使其指向下一个或上一个元素。例如:
int* ptr = somePointer;
ptr++; // 将指针移动到下一个 int
在上述例子中,
ptr
指针被递增,使其指向下一个 int。
4.4、 比较运算
可以通过比较两个指针的大小关系来确定它们是否指向相同的内存区域或者在内存中的位置先后关系。例如:
int* ptr1 = somePointer1;
int* ptr2 = somePointer2;
if (ptr1 < ptr2) {// ptr1 在 ptr2 之前
} else if (ptr1 > ptr2) {// ptr1 在 ptr2 之后
} else {// ptr1 和 ptr2 指向相同的内存位置
}
在上述例子中,我们可以根据指针之间的比较结果执行相应的操作。
需要注意的是,指针运算必须在合法的内存地址范围内进行。还要注意,在进行指针运算时,一定要确保不会发生指针指向未分配的内存或者超出可访问范围的情况。
五、常量与指针
口诀:
左数右指(const在左边时数据为常量,const在右边时指针为常量)
5.1、常量指针
常量指针:
地址可变,指向数据不可变
const int* p;
int const* p;
#include <stdio.h>
int main(){int i = 0;const int* p1 = &i;int const* p2 = &i;*p1 = 1; // compile errorp1 = NULL; // ok*p2 = 2; // compile errorp2 = NULL; // okreturn 0;
}
5.2、指针常量
指针常量:
地址不可变,指向数据可变
int* const p;
#include <stdio.h>
int main(){int i = 0;int* const p3 = &i;*p3 = 3; // okp3 = NULL; // compile errorreturn 0;
}
5.3、常量指针常量
常量指针常量:
地址不可变,指向数据不可变
const int* const p;
#include <stdio.h>
int main(){int i = 0;const int* const p4 = &i;*p4 = 4; // compile errorp4 = NULL; // compile errorreturn 0;
}
六、数组与指针
数组的类型是由元素类型和数组大小共同决定的。
数组声明 | 数组类型 |
---|---|
int array[5] | int[5] |
int matrix[3][3] | int[3][3] |
6.1、重命名数组类型
//格式:
typedef type(name)[size];
//数组类型:
typedef int(AINT5)[5];
typedef doubel(ADOUBLE)[10];
//数组定义:
AINT5 iArray;
ADOUBLE dArray;
6.2、数组指针
数组指针是一个指针,指向对应类型的数组。
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr;
6.2.1、一维数组指针
一维数组指针
是指向一维数组的指针变量。它可以用于访问和操作一维数组中的元素。在C++中,可以使用指针来表示一维数组指针。
#include <iostream>
int main() {int arr[] = {1, 2, 3, 4, 5};int* ptr = arr; // 声明一个指向整型的指针,并指向数组的第一个元素// 访问一维数组中的元素std::cout << *ptr << std::endl; // 输出:1std::cout << *(ptr + 2) << std::endl; // 输出:3// 修改一维数组中的元素*ptr = 100;*(ptr + 2) = 300;// 打印修改后的值std::cout << *ptr << std::endl; // 输出:100std::cout << *(ptr + 2) << std::endl; // 输出:300return 0;
}
在上述示例中,我们首先定义并初始化了一个整数类型的一维数组
int arr[]
。然后,我们声明了一个指向整数的指针int* ptr
。
接下来,我们将指针ptr
指向数组的第一个元素(数组名即为指向第一个元素的指针),这样我们就可以使用ptr
来访问和修改一维数组中的元素了。
使用一维数组指针
时,可以使用解引用操作符*
来访问指针指向的元素值,可以使用指针算术运算(如+
)来访问其他索引处的元素。例如,ptr + 2
将指针移动两个位置,然后通过解引用操作符来访问相应的元素。
6.2.2、二维数组指针
二维数组指针
是指向二维数组的指针变量。它可以用于访问和操作二维数组中的元素。在C++中,可以使用指针数组或双重指针来表示二维数组指针。
#include <iostream>
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; // 输出:1std::cout << ptr[1][2] << std::endl; // 输出:7// 修改二维数组中的元素ptr[0][0] = 100;ptr[1][2] = 200;// 打印修改后的值std::cout << ptr[0][0] << std::endl; // 输出:100std::cout << ptr[1][2] << std::endl; // 输出:200return 0;
}
在上述示例中,我们首先声明并初始化了一个3x4的二维整数数组
int arr[3][4]
。然后,我们声明了一个指向包含4个整数的数组的指针int (*ptr)[4]
。
接下来,我们将指针ptr
指向二维数组arr
,这样我们就可以使用ptr
来访问和修改二维数组中的元素了。
使用二维数组指针时,可以使用类似于二维数组的语法来访问和修改元素,可以使用ptr[i][j]
的形式。在内存中,二维数组元素在一维地址空间中是按行连续存储的。
6.2.3、多维数组指针
多维数组指针
是指向多维数组的指针变量。它可以用于访问和操作多维数组中的元素。在C++中,可以使用多级指针来表示多维数组指针。
#include <iostream>
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; // 输出:100return 0;
}
在上述示例中,我们首先声明并初始化了一个2x3x4的三维整数数组
int arr[2][3][4]
。然后,我们声明了一个指向包含3个二维数组的指针int (*ptr)[3][4]
。
接下来,我们将指针ptr
指向三维数组arr
,这样我们就可以使用ptr
来访问和修改三维数组中的元素了。
使用三维数组指针时,可以使用多级解引用操作符(*
)来访问指针指向的元素值。例如,*(*(*(ptr + 1) + 2) + 3)
将移动指针到下一个二维数组,然后移动到下一个一维数组,最后通过解引用操作符访问该一维数组的第4个元素。
6.3、指针数组
指针数组是一个数组,其中元素都是指针。
type* pArray[n];
int* pointerArray[] = {&a, &b, &c};
// 声明并初始化指针数组
//下面是一个简单的示例,展示了如何声明、初始化和使用指针数组:
#include <iostream>
int main() {int a = 10;int b = 20;int c = 30;int* pointerArray[] = {&a, &b, &c}; // 声明并初始化指针数组// 访问指针数组的元素并打印对应值std::cout << *pointerArray[0] << std::endl; // 输出:10std::cout << *pointerArray[1] << std::endl; // 输出:20std::cout << *pointerArray[2] << std::endl; // 输出:30// 修改指针数组元素的值*pointerArray[0] = 100;*pointerArray[1] = 200;*pointerArray[2] = 300;// 打印修改后的值std::cout << *pointerArray[0] << std::endl; // 输出:100std::cout << *pointerArray[1] << std::endl; // 输出:200std::cout << *pointerArray[2] << std::endl; // 输出:300return 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) |
7.1、重命名函数类型
#include <iostream>// 原始的函数类型声明
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;
}
7.2、函数参数传递为指针
函数参数传递为指针
是一种常见的方式,用于在函数中对变量进行修改或者引用变量的原始值。通过将指针作为参数传递给函数,函数可以直接访问和修改指针所指向的内存地址上的数据。
#include <iostream>// 通过指针修改变量值
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
函数来交换它们的值。
7.3、函数返回值为指针
函数返回值为指针
是一种常见的方式,用于从函数中返回动态分配的内存或者返回指向其他数据结构的指针。通过将函数的返回类型声明为指针,函数可以返回一个指向特定类型数据的内存地址。
#include <iostream>// 返回动态分配内存的指针
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
函数获取全局变量的值,并通过指针访问该变量的值。
7.4、函数指针
函数指针
是指向函数的指针变量。它可以用于调用函数,实现函数的动态绑定和回调等功能。函数指针的声明和使用方式与其他指针类型类似。
#include <iostream>
// 声明一个函数指针类型,该函数接受两个整数参数并返回一个整数
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; // 输出:6return 0;
}
在上述示例中,我们首先通过
typedef
关键字定义了一个名为ArithmeticFunction
的函数指针类型。该函数指针类型接受两个整数参数并返回一个整数。
接下来,我们定义了add
和multiply
两个函数,用于演示函数指针的用法。
在main
函数中,我们声明了一个函数指针变量ptr
,并将其初始化为add
函数。然后,使用函数指针变量调用add
函数,将结果输出。
接着,我们将函数指针变量ptr
指向multiply
函数,并再次使用函数指针调用multiply
函数,将结果输出。
函数指针可以在运行时动态地指向不同的函数,可以将函数指针作为参数传递给其他函数,实现回调功能或根据条件选择不同的函数执行逻辑等。
7.5、回调函数应用
通过函数指针作为参数,使相同的代码实现不同功能
#include <iostream>
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; // 输出:15std::cout << "1 * ... * 5 = " << calculate(a, 5, mul) << std::endl; // 输出:120return 0;
}
八、堆空间与指针
堆空间是程序中预留且可用的内存区域
8.1、动态分配内存
c语言库:
#include <stdlib.h>
申请内存:void* malloc(unsigned bytes)
(判断是否申请成功)
释放内存:void free(void* p)
(不可多次释放)
//int* arr = (int*)malloc(size * sizeof(int)); // 动态分配内存
//free(arr); // 释放动态分配的内存
#include <stdio.h>
#include <stdlib.h>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 <iostream>// 定义一个简单的链表结构
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 <iostream>
#include <string>
// 定义一个简单的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*
指针只能保存地址,不能获取数据。
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*
指针可以与其他数据指针相互赋值,但不可以直接获取内存数据)
相关文章:

「C/C++」C/C++指针详解
✨博客主页何曾参静谧的博客📌文章专栏「C/C」C/C程序设计📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C」C/C程序设计「Win」Windows程序设计「算法」数据结构与算法「File」数据文件格式 目录 一、术语…...

提高电脑寿命的维护技巧与方法分享
在维护电脑运行方面,我有一些自己觉得非常有用的技巧和方法。下面我将分享一些我常用的维护技巧,并解释为什么我会选择这样做以及这样做的好处。 首先,我经常清理我的电脑内部的灰尘。电脑内部的灰尘会影响散热效果,导致电脑发热…...

React常见面试题
React常见面试题 一、React中的样式管理有哪些方法 内联样式:对象,作用于当前组件普通样式表: 作用于全局,文件名是:xxx.scssCSS模块:类似Vue的scoped, 文件名需是:xxx.module.scs…...

C++中数据的输入输出介绍
C中数据的输入输出介绍 C中数据的输入输出涉及到的文件 <iostream>:这是C标准库中最常用的头文件之一,包含了进行标准输入输出操作的类和对象,如std::cin、std::cout、std::endl等。 <iomanip>:该头文件提供了一些用…...

0101日志-运维-mysql
1 错误日志 错误日志(Error Log):错误日志记录了MySQL引擎在运行过程中出现的错误和异常情况。这些错误可能包括启动和关闭问题、数据库崩溃、权限问题等。错误日志对于排查和解决MySQL引擎问题非常有帮助。 改日志默认开启,默认存…...

LabVIEW使用灰度和边缘检测进行视频滤波
LabVIEW使用灰度和边缘检测进行视频滤波 数字图像处理(DIP)是真实和连续世界的离散表示。除此之外,这种数字图像在通信、医学、遥感、地震学、工业自动化、机器人、航空航天和教育等领域变得非常重要。计算机技术越来越需要视频图像的数字图…...

SpringBoot整合WebService
SpringBoot整合WebService WebService是一个比较旧的远程调用通信框架,现在企业项目中用的比较少,因为它逐步被SpringCloud所取代,它的优势就是能够跨语言平台通信,所以还有点价值,下面来看看如何在SpringBoot项目中使…...

【LangChain】向量存储之FAISS
LangChain学习文档 【LangChain】向量存储(Vector stores)【LangChain】向量存储之FAISS 概要 Facebook AI 相似性搜索(Faiss)是一个用于高效相似性搜索和密集向量聚类的库。它包含的算法可以搜索任意大小的向量集,甚至可能无法容纳在 RAM 中…...

小研究 - 主动式微服务细粒度弹性缩放算法研究(三)
微服务架构已成为云数据中心的基本服务架构。但目前关于微服务系统弹性缩放的研究大多是基于服务或实例级别的水平缩放,忽略了能够充分利用单台服务器资源的细粒度垂直缩放,从而导致资源浪费。为此,本文设计了主动式微服务细粒度弹性缩放算法…...

驱动开发相关内容复盘
并发与竞争 并发 多个“用户”同时访问同一个共享资源。 竞争 并发和竞争的处理方法 处理并发和竞争的机制:原子操作、自旋锁、信号量和互斥体。 1、原子操作 原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。 …...

2.2 身份鉴别与访问控制
数据参考:CISP官方 目录 身份鉴别基础基于实体所知的鉴别基于实体所有的鉴别基于实体特征的鉴别访问控制基础访问控制模型 一、身份鉴别基础 1、身份鉴别的概念 标识 实体身份的一种计算机表达每个实体与计算机内部的一个身份表达绑定信息系统在执行操作时&a…...

C++ 注释
程序的注释是解释性语句,您可以在 C 代码中包含注释,这将提高源代码的可读性。所有的编程语言都允许某种形式的注释。 C 支持单行注释和多行注释。注释中的所有字符会被 C 编译器忽略。 C 注释一般有两种: // - 一般用于单行注释。 /* … …...

Spring事务(声明式事务)(Spring的事务,Spring隔离级别,事务传播机制)
目录 一、什么是事务,为什么要用事务 二、Spring声明式事务 🍅 1、Transactional的使用 🎈 事务回滚 🎈注意:异常被捕获,不会发生事务回滚 🍅 2、Transactional 作⽤范围 🍅 …...

Linux运维面试题(四)之Linux服务管理
Linux运维面试题(四)之Linux服务管理 4.1 SSHSSH的登录验证方式SSH的登陆端口(默认22)和监听设置(/etc/ssh/sshd_config)SSH的登录用户限制(/etc/ssh/sshd_config PermitRootLogin)SSH的登录超时设置(/etc/…...

ChatGPT能否撰写科研论文?
ChatGPT,这款被许多人誉为语言处理领域的“黑马”,究竟能否应用于撰写科研论文?近期,以色列理工学院生物学家兼数据科学家Roy Kishony带领的团队,针对这一问题进行了系列研究,其结果已在《Nature》杂志上发…...

2023 电赛 E 题 K210方案
第一章:K210 介绍 K210芯片是一款基于RISC-V架构的嵌入式人工智能芯片,具备低功耗、高性能的特点。它拥有强大的图像处理和机器学习能力,适用于边缘计算设备和物联网应用。为了方便开发者,K210芯片提供了丰富的外设接口ÿ…...

网络知识介绍
一、TCP 传输控制协议,Transmission Control Protocol。 面向广域网的通信协议,跨域多个网络通信时,为两个通信端点之间提供一条具有如下特点的通信方式: 基于流、面向连接、可靠通信方式、网络状况不佳时尽量降低系统由于重传带…...

MapStruct设置全局的ComponentModel
在mapStruct上边,如果我们要切换成非默认的组件模式,常常要在Mapper注释中添加componentModel "spring",如果类太多的了的话,非常麻烦,有没有更好的方式呢,有的,可以在pom中添加一个…...

LinearAlgebraMIT_6_ColumnSpaceAndNullSpace
这节课的两个重点是column space列空间和null space零空间。 x.1 pre-multiply/left multiply and post-multiply/right multiply 对于pre-multiply/left multiply左乘和post-multiply/right multiply右乘,如果用英文的pre-和post-是比较容易理解的, A…...

出版物经营许可办理 出版物许可地址变更 出版物零售延期
一、出版物零售单位设立所需材料 1、申请书 2、营业执照 3、租赁合同 4、主要负责人身 份证 5、出版物经营许可申请表 二、办理出版物经营许可证所要符合的条件 1、有确定的企业名称和经营范围; 2、有出版物业务的经营场地; 3、有出版物业务的组织机构和发行人员。 三、…...

【LeetCode每日一题】——807.保持城市天际线
文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 矩阵 二【题目难度】 中等 三【题目编号】 1572.矩阵对角线元素的和 四【题目描述】 给你一…...

JavaScript--Date(日期)对象
介绍和说明 创建一个Date对象并获取当前日期和时间: 使用new Date()语句可以创建一个表示当前日期和时间的Date对象。它将使用客户端设备上的当前日期和时间。例如:const currentDate new Date(); 获取特定日期的年、月、日、小时、分钟、秒࿱…...

一文讲清多线程与多线程同步
1 多线程 1.1 线程的概念 十多年前,主流观点主张在可能的情况下优先选择多进程而非多线程,如今,多线程编程已经成为编程领域的事实标准。多线程技术在很大程度上改善了程序的性能和响应能力,使其能够更加高效地利用系统资源&…...

《Java-SE-第二十六章》之线程池
前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页:KC老衲爱尼姑的博客主页 博主的github,平常所写代码皆在于此 共勉:talk is cheap, show me the code 作者是爪哇岛的新手,水平很有限&…...

【数据库】将excel数据导入mysql数据库
环境:Windows10 mysql8以上 将你要导入的excel表另存为txt格式 打开txt格式文件,删除表头行并另存为并更改编码方式(由于与数据库的编码不同,会导致导入报错) 通过命令行登录数据库 winr cmd进入 进入装mysql的目录位…...

无涯教程-Lua - repeat...until 语句函数
与 for 和 while 循环(它们在循环顶部测试循环条件)不同,Lua编程中的 repeat ... until 循环语言在循环的底部检查其条件。 repeat ... until 循环与while循环相似,不同之处在于,保证do ... while循环至少执行一次。 repeat...until loop - …...

环形链表 LeetCode热题100
题目 给你一个链表的头节点 head ,判断链表中是否有环。 思路 快慢指针。开始快指针在慢指针前面,当快指针等于慢指针时说明有环,如果快指针指向null时说明无环。 代码 /*** Definition for singly-linked list.* struct ListNode {* …...

使用python将每组两行数据合并一行
1、使用场景 将有规律的每组(一组2行)的单数行和双数行合并为一行,以空格分割。 比如使用pssh批量得出的结果,想让ip行和结果行合并为一行(前提如上所述) [rootk8s-master1 tmp]# pssh -h iplist -i hostname [1] 18:12:42 [SU…...

14-1_Qt 5.9 C++开发指南_网络编程及主机信息查询_HostInfo
Qt 网络模块提供了用于编写 TCP/IP 客户端和服务器端程序的各种类,如用于 TCP 通信的QTcpSocket 和 QTcpServer,用于 UDP 通信的 QUdpSocket,还有用于实现 HTTP、FTP 等普通网络协议的高级类如 QNetworkRequest,QNetworkReply 和Q…...

【iOS】通知原理
我们可以通过看通知的实现机制来了解通知中心是怎么实现对观察者的引用的。由于苹果对Foundation源码是不开源的,我们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:GNUStep源码GitHub下载地址, 具体源码可以进行查看。 通知的主要流程 通知全…...