MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++引用和指针自增运算结果分析

2021-06-172.1k 阅读

C++引用和指针自增运算结果分析

一、C++ 引用基础

在C++ 中,引用(reference)是给已存在变量起的一个别名,它与被引用的变量共享同一块内存地址。定义引用时必须初始化,一旦初始化完成,引用就不能再绑定到其他变量。例如:

int num = 10;
int& ref = num;

这里ref就是num的引用,对ref的任何操作就等同于对num的操作。例如:

ref = 20;
std::cout << num << std::endl; 

输出结果将是20,因为refnum指向同一块内存,对ref赋值就是对num赋值。

二、C++ 指针基础

指针(pointer)是一个变量,其值为另一个变量的内存地址。与引用不同,指针可以在初始化后重新指向其他变量。定义指针时,需要使用*符号。例如:

int num = 10;
int* ptr = &num;

这里ptr是一个指向num的指针,&符号用于获取变量的地址。可以通过解引用指针来访问它所指向的变量,使用*符号。例如:

*ptr = 20;
std::cout << num << std::endl; 

同样会输出20,因为通过解引用ptr修改了num的值。

三、自增运算的基本概念

自增运算符++有两种形式:前置自增++var和后置自增var++。对于普通变量,前置自增先将变量的值加1,然后返回加1后的值;后置自增先返回变量原来的值,然后再将变量的值加1。例如:

int a = 5;
int b = ++a; 
std::cout << "a: " << a << ", b: " << b << std::endl; 
// 输出: a: 6, b: 6

int c = 5;
int d = c++; 
std::cout << "c: " << c << ", d: " << d << std::endl; 
// 输出: c: 6, d: 5

四、引用的自增运算

当自增运算作用于引用时,由于引用本身只是一个别名,所以自增运算实际上是作用于引用所绑定的变量上。无论是前置自增还是后置自增,其效果与直接对被引用变量进行自增运算相同。

  1. 前置自增
int num = 10;
int& ref = num;
int result1 = ++ref;
std::cout << "num: " << num << ", result1: " << result1 << std::endl; 

在这段代码中,++ref先将num的值加1,然后返回加1后的值。所以numresult1的值都为11。 2. 后置自增

int num = 10;
int& ref = num;
int result2 = ref++;
std::cout << "num: " << num << ", result2: " << result2 << std::endl; 

这里ref++先返回num原来的值10,然后再将num的值加1。所以result210num的值变为11

五、指针的自增运算

指针的自增运算与引用的自增运算有很大不同。指针自增时,它会根据其所指向的数据类型的大小移动到下一个内存地址。

  1. 指向基本数据类型的指针自增
int num = 10;
int* ptr = &num;
std::cout << "ptr before increment: " << ptr << std::endl; 
++ptr;
std::cout << "ptr after increment: " << ptr << std::endl; 

在32位系统中,int类型通常占用4个字节。所以++ptr后,ptr会移动4个字节的内存地址。如果ptr初始值为0x1000,那么自增后ptr的值可能变为0x1004(这里只是示例,实际内存地址取决于系统分配)。 2. 前置自增

int arr[] = {1, 2, 3, 4, 5};
int* ptr1 = arr;
int result3 = *(++ptr1);
std::cout << "result3: " << result3 << std::endl; 

这里++ptr1先将ptr1移动到下一个元素的地址,然后解引用ptr1。所以result3的值为2。 3. 后置自增

int arr[] = {1, 2, 3, 4, 5};
int* ptr2 = arr;
int result4 = *(ptr2++);
std::cout << "result4: " << result4 << std::endl; 

ptr2++先解引用ptr2,返回ptr2当前指向的值1,然后再将ptr2移动到下一个元素的地址。所以result4的值为1

六、指向数组的指针自增

  1. 一维数组
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr;
for (int i = 0; i < 5; ++i) {
    std::cout << *ptr << " ";
    ++ptr;
}
std::cout << std::endl; 

这段代码通过指针遍历一维数组。每次++ptr,指针指向下一个数组元素的地址,从而依次输出数组元素。 2. 二维数组

int twoDArr[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
int* ptr2D = &twoDArr[0][0];
for (int i = 0; i < 9; ++i) {
    std::cout << *ptr2D << " ";
    ++ptr2D;
}
std::cout << std::endl; 

对于二维数组,ptr2D同样按照内存顺序移动。二维数组在内存中是按行存储的,所以++ptr2D依次访问每个元素。

七、指针和引用自增在函数参数中的应用

  1. 引用作为函数参数自增
void incrementByRef(int& num) {
    ++num;
}

int main() {
    int num = 10;
    incrementByRef(num);
    std::cout << "num after increment: " << num << std::endl; 
    return 0;
}

在这个例子中,函数incrementByRef接受一个引用参数。对引用num的自增运算实际上是对传递进来的实参进行自增,所以在main函数中输出的num值为11。 2. 指针作为函数参数自增

void incrementByPtr(int* ptr) {
    ++(*ptr);
}

int main() {
    int num = 10;
    incrementByPtr(&num);
    std::cout << "num after increment: " << num << std::endl; 
    return 0;
}

这里函数incrementByPtr接受一个指针参数。通过解引用指针并自增,实现对指针所指向变量的自增。在main函数中输出的num值同样为11

八、复杂数据结构中的指针和引用自增

  1. 链表中的指针自增 链表是一种常见的动态数据结构,每个节点包含数据和指向下一个节点的指针。在遍历链表时,需要通过指针自增(移动到下一个节点)来访问各个节点。
struct ListNode {
    int data;
    ListNode* next;
    ListNode(int val) : data(val), next(nullptr) {}
};

void printList(ListNode* head) {
    ListNode* current = head;
    while (current != nullptr) {
        std::cout << current->data << " ";
        current = current->next; 
    }
    std::cout << std::endl; 
}

int main() {
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    printList(head);
    return 0;
}

printList函数中,current = current->next就相当于指针的“自增”操作,使指针移动到下一个节点。 2. 类中引用成员的自增

class MyClass {
public:
    MyClass(int& value) : refValue(value) {}
    void incrementRef() {
        ++refValue;
    }
    int getRefValue() const {
        return refValue;
    }
private:
    int& refValue;
};

int main() {
    int num = 10;
    MyClass obj(num);
    obj.incrementRef();
    std::cout << "num in MyClass: " << obj.getRefValue() << std::endl; 
    return 0;
}

MyClass类中,有一个引用成员refValueincrementRef函数对引用进行自增,实际上是对绑定的外部变量num进行自增。

九、引用和指针自增的性能考虑

  1. 引用自增 由于引用只是别名,自增引用本质上就是自增被引用的变量,不存在额外的间接寻址开销。所以在性能上,引用自增与直接对变量自增几乎没有区别。
  2. 指针自增 指针自增涉及到内存地址的计算和移动。虽然现代编译器会对指针运算进行优化,但相比于引用自增,指针自增可能会有一些额外的开销,特别是在频繁进行指针自增操作时。例如,在遍历大型数组时,使用指针自增可能会比使用引用(如果可行的话)稍微慢一些,因为指针需要额外计算内存地址。

十、引用和指针自增的易错点

  1. 空指针自增 对空指针进行自增是未定义行为。例如:
int* ptr = nullptr;
++ptr; 

这行代码会导致未定义行为,程序可能崩溃或者产生不可预测的结果。 2. 悬空引用和悬空指针 当引用或指针所指向的对象被释放后,继续使用它们进行自增运算也会导致未定义行为。例如:

{
    int* ptr = new int(10);
    int& ref = *ptr;
    delete ptr;
    ++ref; 
    ++ptr; 
}

delete ptr后,ptr变成悬空指针,ref变成悬空引用。对它们进行自增运算会导致未定义行为。

十一、总结引用和指针自增运算的差异

  1. 运算对象 引用自增直接作用于被引用的变量;而指针自增是移动指针所指向的内存地址,根据指针类型移动相应的字节数。
  2. 语法表现 引用自增语法与普通变量自增相同;指针自增需要注意解引用符号与自增符号的结合顺序,前置自增++(*ptr)先自增所指向变量,后置自增(*ptr)++先返回当前值再自增。
  3. 应用场景 引用自增常用于函数参数传递和简单变量操作,保持与变量直接操作的一致性;指针自增在动态数据结构(如链表、树等)遍历以及需要灵活改变指向的场景中应用广泛。

在实际编程中,深入理解引用和指针自增运算的区别和特性,有助于编写出更高效、更健壮的C++ 代码。无论是处理基本数据类型,还是复杂的数据结构,正确运用引用和指针自增运算都能提升程序的性能和可读性。同时,注意避免易错点,防止出现未定义行为,确保程序的稳定性和可靠性。通过不断实践和总结,开发者可以熟练掌握这两种重要的C++ 特性,并在各种编程场景中灵活运用。