基础知识|C++|指针、引用、右值、右值引用

一、什么是指针

在 C++ 中,指针是一种变量类型,它存储的是内存地址而不是实际的数据值。

指针的基本概念

  1. 声明指针

    • 指针变量必须声明其类型,这决定了它可以指向哪种类型的变量。
    • 例如,int* p; 声明了一个指向整型的指针 p
  2. 初始化指针

    • 指针可以被初始化为 nullptr(C++11 及以后版本)或 NULL(C++98 和 C++03)。
    • 也可以指向一个已存在的变量的地址。
    • 例如,int x = 10; int* p = &x;
  3. 解引用指针

    • 使用 * 运算符访问指针指向的内存位置的值。
    • 例如,int value = *p; 将 p 指向的整数值赋给 value
  4. 指针运算

    • 可以对指针进行加减运算,以指向数组中的不同元素。
    • 例如,int arr[10]; int* p = arr; p++; 使 p 指向数组中的下一个元素。
  5. 指针和数组

    • 在 C++ 中,数组名实际上是一个指向数组第一个元素的常量指针。
    • 例如,int arr[10]; int* p = arr; 使 p 指向 arr 的第一个元素。

二、什么是引用

在 C++ 中,引用(Reference)是一种特殊类型的变量,它充当另一个变量的别名。引用在声明时必须被初始化,并且一旦初始化后就不能改变引用的对象。引用通常用于传递大对象作为函数参数,返回多个值,或者作为类成员的替代品。下面详细介绍引用的概念和用法。

引用的基本概念

  1. 声明引用

    • 引用声明语法类似于指针,但不需要使用星号 *
    • 例如,int& ref = x; 声明了一个引用 ref,它是整型变量 x 的别名。
  2. 初始化引用

    • 引用在声明时必须被初始化,并且不能指向 nullptr
    • 例如,int x = 10; int& ref = x;
  3. 解引用引用

    • 不需要使用 * 运算符来解引用引用,可以直接使用引用名。
    • 例如,ref = 20; 直接修改了 x 的值。
  4. 引用和指针的区别

    • 引用不是真正的变量,而是一个别名。
    • 一旦引用被初始化,就不能改变它所引用的对象。
    • 引用不能被赋值为 nullptr,而指针可以。

引用和指针做函数参数: 

#include <iostream>

// 使用指针交换两个整数
void swapPtr(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 使用引用交换两个整数
void swapRef(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10;
    int y = 20;

    // 使用指针交换
    std::cout << "Before swap with pointers: x = " << x << ", y = " << y << std::endl;
    swapPtr(&x, &y);
    std::cout << "After swap with pointers: x = " << x << ", y = " << y << std::endl;

    // 使用引用交换
    x = 10;  // 重置 x 和 y
    y = 20;
    std::cout << "Before swap with references: x = " << x << ", y = " << y << std::endl;
    swapRef(x, y);
    std::cout << "After swap with references: x = " << x << ", y = " << y << std::endl;

    return 0;
}

Before swap with pointers: x = 10, y = 20

After swap with pointers: x = 20, y = 10

Before swap with references: x = 10, y = 20

After swap with references: x = 20, y = 10

指针作为返回值 vs.引用作为返回值 :

#include <iostream>

// 使用指针返回数组的最大值
int* findMaxPtr(int arr[], int size) {
    int* maxPtr = &arr[0];
    for (int i = 1; i < size; ++i) {
        if (arr[i] > *maxPtr) {
            maxPtr = &arr[i];
        }
    }
    return maxPtr;
}

// 使用引用返回数组的最大值
int& findMaxRef(int arr[], int size) {
    int& maxRef = arr[0];
    for (int i = 1; i < size; ++i) {
        if (arr[i] > maxRef) {
            maxRef = arr[i];
        }
    }
    return maxRef;
}

int main() {
    int arr[] = {10, 20, 5, 15, 25};
    int size = sizeof(arr) / sizeof(arr[0]);

    // 使用指针返回最大值
    int* maxPtr = findMaxPtr(arr, size);
    std::cout << "Max value with pointer: " << *maxPtr << std::endl;

    // 使用引用返回最大值
    int& maxRef = findMaxRef(arr, size);
    std::cout << "Max value with reference: " << maxRef << std::endl;

    return 0;
}

Max value with pointer: 25

Max value with reference: 25 

三、什么是右值

        在 C++ 中,右值(R-value)是指表达式或对象在赋值操作中的右侧部分。右值通常代表一个临时值或一个即将消亡的对象。右值可以分为两种主要类型:普通右值和将逝右值(Xvalue)。

(一)右值的分类

  1. 普通右值

    • 普通右值通常指的是那些不能被取地址的对象。
    • 这包括字面量(如 10"hello")、临时对象(如函数返回值或表达式的结果)和将要消亡的对象。
  2. 将逝右值(Xvalue)

    • 将逝右值是一种特殊的右值,表示一个即将消亡的对象,可以被移动。
    • 在 C++11 中引入了移动语义,允许将逝右值被移动而非复制,以提高效率。
    • 将逝右值可以出现在移动赋值操作符的左侧。

(二)右值示例

#include <iostream>
#include <string>

// 一个简单的类
class MyClass {
public:
    MyClass() : value(0) {}
    MyClass(int v) : value(v) {}
    MyClass(const MyClass&) { std::cout << "Copy constructor called." << std::endl; }
    MyClass(MyClass&& other) noexcept : value(other.value) {
        std::cout << "Move constructor called." << std::endl;
        other.value = 0; // 清空原对象
    }
    MyClass& operator=(MyClass&& other) noexcept {
        std::cout << "Move assignment operator called." << std::endl;
        value = other.value;
        other.value = 0; // 清空原对象
        return *this;
    }

    int value;
};

int main() {
    // 普通右值
    MyClass x = 10;  // 使用字面量初始化
    MyClass y = MyClass(20);  // 使用临时对象初始化

    // 将逝右值
    MyClass z = std::move(x);  // 使用 std::move 将 x 移动到 z
    MyClass w = std::move(y);  // 使用 std::move 将 y 移动到 w

    // 输出结果
    std::cout << "x.value: " << x.value << std::endl;
    std::cout << "y.value: " << y.value << std::endl;
    std::cout << "z.value: " << z.value << std::endl;
    std::cout << "w.value: " << w.value << std::endl;

    return 0;
}

Copy constructor called.

Move constructor called.

Move assignment operator called.

x.value: 0

y.value: 0

z.value: 10

w.value: 20

  1. 普通右值

    • x = 10; 中的 10 是一个普通右值。
    • y = MyClass(20); 中的 MyClass(20) 是一个普通右值。
    • 普通右值通常代表一个临时值或即将消亡的对象。
  2. 将逝右值

    • z = std::move(x); 中的 std::move(x) 是一个将逝右值。
    • w = std::move(y); 中的 std::move(y) 是一个将逝右值。
    • 将逝右值可以被移动而非复制,以提高效率。

四、右值引用是什么

        右值引用是为一个临时变量取别名,它只能绑定到一个临时变量或表达式(将亡值)上。实际开发中我们可能需要对右值进行修改(实现移动语义时就需要)而右值引用可以对右值进行修改。

        为什么:1.移动语义;2.完美转发;3.拓展可变参数模板,实现更加灵活的模板编程。

1.移动语义

右值引用可以绑定到临时对象、表达式等右值上,这些右值在生命周期结束后就会被销毁,因此可以在右值引用中窃取其资源,从而避免昂贵的复制操作,实现高效的移动语义。


#include <iostream>
#include <vector>
#include <string>
 
class Resource {
public:
    Resource(int size) { std::cout << "Resource created with size " << size << "\n"; }
    Resource(Resource&& other) noexcept { std::cout << "Resource moved\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
};
 
void processResource(Resource&& r) {
    // 处理资源
    std::cout << "Resource is being processed\n";
}
 
int main() {
    Resource r(10); // 创建一个 Resource 对象
    processResource(std::move(r)); // 将 r 转换为右值引用并传递给函数
    // 此时 r 已经是一个无效对象,因为它的资源已经被移动走了


                       

2.完美转发

右值引用可以绑定到任何类型的右值上,可以将其作为参数传递给函数,并在函数内部将其“转发”到其他函数中,从而实现完美转发。

#include <iostream>
#include <utility> // For std::forward
 
// 模拟一个可以接收左值引用和右值引用的函数
void process(int& x) {
    std::cout << "process(int&) - Lvalue reference" << std::endl;
}
 
void process(int&& x) {
    std::cout << "process(int&&) - Rvalue reference" << std::endl;
}
 
// 模板函数,使用完美转发将参数转发给process函数
template<typename T>
void wrapper(T&& arg) {
    // 使用std::forward来转发参数,保持其原始的类型和值类别
    process(std::forward<T>(arg));
}
 
int main() {
    int a = 10;
    wrapper(a);  // 编译器将调用 process(int&),因为a是左值
    wrapper(10); // 编译器将调用 process(int&&),因为10是临时对象(右值)
}

  • 17
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值