C++指针

一. 指针的基本概念

定义:指针是一种变量,其值为另一个变量的地址。在C++中,可以通过&运算符获取变量的地址,这个地址可以存储在指针变量中。例如,对于一个整数变量int num = 10;,可以定义一个指向num的指针int *ptr = #。

语法指针类型的声明形式为类型* 指针变量名,其中类型表示指针所指向变量的类型。

二. 指针的操作

解引用:通过*运算符对指针进行解引用,可以访问指针所指向的变量的值。例如,对于上面定义的ptr,*ptr就等于num的值,即10。可以通过解引用指针来修改所指向变量的值,如*ptr = 20;,此时num的值也变为20。

指针的算术运算

对于指向数组元素的指针,可以进行算术运算。如果int arr[] = {1, 2, 3};,int *p = arr;(此时p指向arr[0]),那么p + 1指向arr[1]。指针的算术运算根据指针所指向的类型来调整地址偏移量,例如对于int类型的指针,p+1实际是将地址增加sizeof(int)字节。

两个指针相减可以得到它们之间元素的个数(在指向同一个数组元素的情况下)。例如,如果int *q = &arr[2];,那么q - p的值为2,表示q和p之间相隔2个元素。

三. 指针与数组

在C++中,数组名在很多情况下可以看作是一个指向数组首元素的指针。例如,对于int arr[] = {1, 2, 3};,arr和&arr[0]在数值上是相等的。可以使用指针来遍历数组,如:

int arr[] = {1, 2, 3};
int *p = arr;
for (int i = 0; i < 3; i++) {
    std::cout << *p << " ";
    p++;
}

也可以将数组作为函数参数传递给函数,在函数内部通过指针来操作数组。例如:

void printArray(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        std::cout << arr[i] << " ";
    }
}
int main() {
    int arr[] = {1, 2, 3};
    printArray(arr, 3);
    return 0;
}

四. 指针与函数

函数指针:函数指针是指向函数的指针。函数指针的声明形式为返回值类型 (*函数指针变量名)(参数列表)。例如,int (*funcPtr)(int, int);声明了一个指向返回值为int,接受两个int类型参数的函数的指针。可以将函数名赋值给函数指针,然后通过函数指针来调用函数。例如:

int add(int a, int b) {
    return a + b;
}
int main() {
    int (*funcPtr)(int, int) = add;
    int result = funcPtr(3, 5);
    std::cout << "The result is: " << result << std::endl;
    return 0;
}

指针作为函数参数:指针可以作为函数的参数,这样在函数内部可以修改函数外部变量的值。例如:

void increment(int *num) {
    (*num)++;
}
int main() {
    int num = 10;
    increment(&num);
    std::cout << "The new value of num is: " << num << std::endl;
    return 0;
}

五. 空指针和野指针

空指针:空指针是一个特殊的指针值,表示指针不指向任何有效的对象或函数。在C++中,可以用nullptr(C++11及以上)或者NULL(在C++中NULL通常被定义为0)来表示空指针。例如,int *p = nullptr;。对空指针进行解引用操作是未定义行为,会导致程序错误。

野指针:野指针是指指向未初始化或者已释放内存区域的指针。例如:

int *p;
// p没有被初始化就使用是野指针
*p = 10;

或者:

int *q = new int;
delete q;
// q在释放后再使用就是野指针
*q = 20;

使用野指针同样会导致未定义行为,如程序崩溃、数据损坏等。为了避免野指针,应该确保指针在使用前被正确初始化,并在释放内存后将指针设置为nullptr。

六. 指针的类型转换

在C++中,可以进行指针类型的转换,但这种转换需要谨慎进行,因为可能会导致未定义行为。

隐式转换:在某些情况下,C++允许隐式的指针类型转换。例如,const指针和非const指针之间可以进行一些转换。如果有const int *p;,可以将其赋值给int *q是不允许的(因为这可能会导致通过q修改p所指向的常量对象),但是可以将int *r赋值给const int *s(这种转换是安全的,因为不会通过s修改所指向的对象)。

显式转换(强制类型转换):使用reinterpret_cast、static_cast等类型转换操作符可以进行指针类型的显式转换。例如,reinterpret_cast可以用于在不同类型的指针之间进行转换,如将int *转换为void *或者反之。但这种转换通常需要对底层的内存布局和数据类型有深入的理解,不当使用会导致程序错误。

int num = 10;
int *p = &num;
void *v = reinterpret_cast<void *>(p);
int *q = reinterpret_cast<int *>(v);

七. 指针的类型

1. 基本数据类型指针:

整型指针:int *ptr,指向int类型的变量。它可以存储一个int类型变量的内存地址,通过解引用操作符*可以访问和修改该变量的值。

浮点型指针:float *ptr,指向float类型的变量,用于操作浮点型数据。

字符型指针:char *ptr,指向一个char类型的变量或者字符数组的首地址。字符指针在处理字符串相关操作时非常常见,比如字符串的输入、输出、复制等操作。

布尔型指针:bool *ptr,指向bool类型的变量,bool类型只有true和false两个值,布尔型指针可以用来操作和管理布尔类型的变量。

2. 数组指针:

数组名本身就是一个指向数组首元素的指针,例如int arr[5];,这里的arr就可以看作是一个指向int类型的指针,指向数组的第一个元素。

也可以定义一个指向整个数组的指针,例如int (*ptr)[5];,这里的ptr是一个指向包含 5 个int元素的数组的指针。这种指针在处理多维数组或者需要将数组作为参数传递给函数时比较有用。

3. 函数指针:函数指针是指向函数的指针变量。定义格式为返回类型 (*指针变量名)(参数列表);。例如int (*fp)(int, int);定义了一个函数指针fp,它可以指向一个接收两个int类型参数并返回int类型值的函数。函数指针可以用来实现函数的回调、动态调用函数等功能。

4. 指针的指针(多级指针):也称为间接指针或二级指针等。例如int **ptr;,这里的ptr是一个指向int类型指针的指针。也就是说,ptr所存储的是另一个指针的地址,而那个指针又指向一个int类型的变量。多级指针可以用于处理复杂的数据结构,比如链表、树等。

5. 常量指针和指向常量的指针:

常量指针:指针本身是一个常量,即指针所指向的地址不能被改变,但可以通过该指针修改其所指向的变量的值。定义方式为数据类型 * const 指针变量名;,例如int * const p = &a;,这里p是一个常量指针,指向int类型的变量a,之后不能再让p指向其他的变量。

指向常量的指针:指针指向一个常量,即不能通过该指针修改其所指向的变量的值,但指针本身可以指向其他的变量。定义方式为const 数据类型 * 指针变量名;,例如const int *p = &a;,这里p是一个指向常量的指针,不能通过p来修改a的值,但p可以指向其他的int类型的常量或变量。

6. 空指针:空指针不指向任何对象,它的值为nullptr(在 C++11 及以上版本)或NULL(在较早的 C++版本中)。在程序中,当一个指针尚未初始化或者需要表示一个无效的指针时,可以使用空指针。

7. 智能指针:

std::unique_ptr:对其所拥有的堆内存具有唯一拥有权,它的引用计数永远为 1,在其销毁时会自动释放其所拥有的堆内存。

std::shared_ptr:持有的资源可以在多个shared_ptr之间共享,每多一个shared_ptr对资源的引用,引用计数就加 1;每一个指向shared_ptr对象析构时,引用计数则减 1,最后一个shared_ptr对象析构时,如果发现资源计数为 0,则释放其持有的资源。

std::weak_ptr:是一种弱引用的智能指针,它不会增加所指向对象的引用计数,主要用于解决std::shared_ptr可能导致的循环引用问题。

C++编程语言基础

C++指针