C++高频知识点(三)
文章目录
- 11. 请解释const关键字在C++中的作用
- 12. 解释浅拷贝和深拷贝,并提供相应代码示例。
- 浅拷贝示例:
- 深拷贝示例:
- 13. 解释指针与数组之间的关系,如何通过指针遍历数组?
- 14. 解释auto关键字在C++11中的作用及其使用场景。
- 15. 什么是移动语义(Move Semantics)?它有什么优势?
11. 请解释const关键字在C++中的作用
const 关键字在 C++ 中有多种用途,主要用于定义常量或确保某些变量的值不被修改。以下是 const 在 C++ 中的一些主要用途:
- 定义常量:const 可以用来定义常量,即其值在程序运行过程中不可改变的量。例如:
const int a = 10;
这里,a 是一个常量,其值被设定为 10,并且不能在程序运行过程中被修改。
- 指针与 const:const 在指针的使用中特别有用,可以用它来保证指针指向的内容不被修改,或者保证指针本身的值(即内存地址)不被修改,或者两者都不被修改。
- 指向常量的指针(指针指向的内容不能被修改):
const int *p = &a;
- 常量指针(指针本身的值不能被修改):const的右边 离谁最近 就是修饰谁
这里,p 是一个指针,它指向一个常量整数。你不能通过 p 来修改它所指向的值。
int b = 20;
int *const q = &b;
这里,q 是一个常量指针,指向一个整数。你不能改变 q 的值(即它不能指向其他地址),但可以通过 q 来修改它所指向的内容。
- 函数参数与 const:在函数参数中使用 const 可以保证传递给函数的参数在函数体内不会被修改,从而增强代码的可读性和安全性。例如:
int CalculateSomething(const int &a, const int& b) { // ... do something ...
}
在这个函数中,a 和 b 的值都不能被修改。
- const 成员函数:在类的成员函数声明后面添加 const 关键字,表示这个成员函数不会修改类的任何成员变量(除了被声明为 mutable 的成员)。这有助于确保成员函数不会意外地修改对象的状态。例如:
class MyClass {
public: int GetValue() const { return value_; }
private: int value_;
};
在这个例子中,GetValue 函数是一个常量成员函数,它保证不会修改 MyClass 的任何成员变量。
- constexpr:C++11 引入了 constexpr 关键字,它用于在编译时计算常量表达式的值。这与 const 相似,但 constexpr 强调表达式的值在编译时就是已知的。例如:
constexpr int x = 2 * 3; // x 在编译时就被计算为 6
12. 解释浅拷贝和深拷贝,并提供相应代码示例。
在C++中,浅拷贝和深拷贝是关于对象复制的重要概念。
浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果字段是基本数据类型,那么对该字段执行逐位拷贝。如果字段是引用类型(例如指针或引用),则拷贝引用但不拷贝引用的对象。因此,原始对象及其副本引用同一个对象。
深拷贝:将对象复制到另一个对象中,同时递归地复制对象中的引用类型的属性。这意味着,如果原对象内部的子对象发生变化,不会影响到已经复制出去的对象。
下面,我们分别用代码示例来解释浅拷贝和深拷贝:
浅拷贝示例:
#include <iostream>
#include <bits/stdc++.h>class StringShallowCopy {
public:char* str;// 构造函数 StringShallowCopy(const char* s = "") {if(s) {str = new char[strlen(s) + 1];strcpy(str, s);} else {str = nullptr;}}// 拷贝构造函数 - 浅拷贝 StringShallowCopy(const StringShallowCopy& other) {// 注意,这里只是简单地拷贝了指针,没有拷贝指针指向的内容 str = other.str;}~StringShallowCopy() {delete[] str;}void print() {std::cout<< str<< std::endl;}
};int main() {StringShallowCopy s1{"Hello"};// 使用拷贝构造函数进行浅拷贝StringShallowCopy s2{s1};s2.print();// 这里会出现问题,因为s1和s2的str指向同一块内存,当s1析构时会释放这块内存, // 然后s2析构时会再次尝试释放同一块内存,导致未定义行为(通常是程序崩溃)return 0;
}
注意:上面的浅拷贝示例中,析构函数会导致问题,因为两个对象的str成员指向同一块内存。在实际编程中,应避免这种情况。
深拷贝示例:
#include <iostream>
#include <cstring> class StringDeepCopy {
public:char* str;// 构造函数StringDeepCopy(const char* s = "") {if(s) {str = new char[strlen(s) + 1];strcpy(str, s);} else {str = nullptr;}}// 拷贝构造函数 - 深拷贝StringDeepCopy(const StringDeepCopy& other) {if(this != &other) { // 防止自我赋值if(other.str) {str = new char[strlen(other.str) + 1];strcpy(str, other.str);} else {str = nullptr;}}}~StringDeepCopy() {delete[] str; // 释放内存} void print() {std::cout << str<< std::endl;}
};int main() {StringDeepCopy s1{"Hello"};StringDeepCopy s2{s1}; // 调用拷贝构造函数进行深拷贝s2.print();// 这里没有问题,因为s1和s2的str指向不同的内存块return 0;
}
在上面的深拷贝示例中,StringDeepCopy类的拷贝构造函数为str成员分配了新的内存,并将原始字符串的内容复制到新内存中。这样,每个对象都有自己的str副本,互不影响。
13. 解释指针与数组之间的关系,如何通过指针遍历数组?
指针和数组在C/C++语言中有着紧密的关系。简单来说,数组名可以被看作是指向数组第一个元素的指针。这种关系使得我们可以通过指针来访问和遍历数组。
#include <stdio.h> int main() { int arr[] = {1, 2, 3, 4, 5}; int *p = arr; // 指针p指向数组的第一个元素 int i; for (i = 0; i < 5; i++) { printf("%d ", *(p + i)); // 使用指针算术来访问数组元素 } printf("\n"); // 或者,你也可以这样遍历数组: for (p = arr; p < arr + 5; p++) { printf("%d ", *p); // 通过解引用指针p来访问当前指向的元素 } printf("\n"); return 0;
}
14. 解释auto关键字在C++11中的作用及其使用场景。
在C++11中,auto关键字被引入用于自动类型推导,即让编译器根据初始化表达式自动推导出变量的类型。这一特性极大地简化了代码书写,减少了类型声明的繁琐性,同时也增强了代码的可读性和可维护性。
15. 什么是移动语义(Move Semantics)?它有什么优势?
下面是一个简单的代码样例,展示了如何使用std::move来移动一个std::string对象:
#include <iostream>
#include <string>
#include <utility> // 为了使用 std::move class MyString {
public:MyString(const std::string&s): data(new std::string(s)) {}// 移动构造函数MyString(MyString&& other) noexcept: data(other.data) {other.data = nullptr; // 将源对象的指针置为nullptr,避免析构时释放内存}// 移动赋值运算符MyString& operator=(MyString&& other) noexcept {if(this != &other) {delete data; // 释放当前对象的内存data = other.data; // 转移资源other.data = nullptr; // 将源对象的指针置为nullptr,避免析构时释放内存}return *this;}// 析构函数~MyString() {delete data; // 释放内存}std::string& getdata() const{return *data; // 返回字符串数据}private:std::string* data; // 使用指针来管理字符串数据
};int main() {MyString s1("Hello, World!");MyString s2 = std::move(s1); // 使用移动赋值运算符转移资源// 此时s1的资源已经被移动到s2,所以s1现在是一个空对象(或无效对象) // 尝试访问s1可能会导致未定义行为 // 注意:在实际应用中,你应该避免在移动后使用s1,除非你确定它是安全的 // 输出s2以验证资源已经被移动 std::cout<< s2.getdata()<< std::endl;return 0;
}
之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!