C++笔记(下)
八、引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。eg:类似于给你取一个新的呼叫名,比如你叫李华,我可以叫你小华(当公司只有你一个带华字的),等于说我可以通过这个呼叫你(直接访问)。
引用相当于给这个内存中的数据提供了一个新的变量名。(我脑子存储了你叫小华)
引用很容易和指针混淆。
引用和指针的区别
1.不存在空引用。引用必须连接到一块合法的内存。
2.一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
3.引用必须在创建时被初始化。指针可以在任何时间被初始化。
可以说变量名是变量在内存中的第一 个名字,对它的引用是其第二个名字;
int a=6; int* p=&a; *p=20;可以为 a声明引用变量
int& r = a; double& s = d;&读作引用。第一个声明读作” r 是一个初始化为 i 的整型引用“,第二个同理。
| 特性 | 引用 (Reference) | 指针 (Pointer) |
| 声明语法 | int& ref = var; | int* ptr = &var |
| 是否可为空 | 不可为空,必须绑定有效对象 | 可为 nullptr |
| 是否可重新绑定 | 一旦绑定,不可更改目标 | 可随时指向其他变量或 nullptr |
| 访问目标值 | 直接使用 ref | 需解引用 *ptr |
| 内存地址存储 | 不存储地址,是变量的别名 | 存储变量的地 |
#include <iostream> using namespace std; int main() { int value = 10; // 引用部分 int& ref = value; // ref 是 value 的别名 ref = 20; // 修改 ref 等同于修改 value cout << "引用修改后 value = " << value << endl; // 输出: 20 // 尝试重新绑定引用(不允许!编译报错) // int otherValue = 30; // ref = otherValue; // 这只是赋值,不是重新绑定!ref 仍是 value 的别名 // 指针部分 int* ptr = &value; // ptr 存储 value 的地址 *ptr = 30; // 修改指针指向的内容 cout << "指针修改后 value = " << value << endl; // 输出: 30 // 重新指向另一个变量(允许) int newValue = 40; ptr = &newValue; // ptr 现在指向 newValue *ptr = 50; // 修改 newValue cout << "newValue = " << newValue << endl; // 输出: 50 // 空指针( 允许) ptr = nullptr; // ptr 不指向任何对象 // cout << *ptr; // 不要这样做!会导致崩溃(解引用空指针) // 地址对比 cout << "\n 内存地址: " << endl; cout << "value 地址: " << &value << endl; cout << "ref 地址: " << &ref << endl; // 与 value 地址相同 cout << "ptr 地址: " << &ptr << endl; // 指针本身的地址 cout << "ptr 指向的地址: " << ptr << endl; // 当前 ptr是nullptr return 0; }这是一个例子
#include<iostream> using namespace std; double val[]={10.1,12.6,33.1,24.1,50.0}; double& setValue(int i) { double& ref = val[i]; return ref; } int main() { setValue (3) = 90.9; cout<< vals[3] ; return 0; }返回引用时,要注意被引用的对象不能超出作用域。所以返回一个局部变量的引用是不合法的。
int& fun(){ int q; // return q; //编译发生错误 static int x; return x; //安全,在函数作用域外依然有效 }为什么通过函数可以修改数组?
核心原理:引用传递
setValue返回的是val[i]的引用(double&),而不是它的拷贝。- 对引用的修改会直接影响原始数组元素,因为引用是原始数据的“别名”。
| 函数类型 | 示例代码 | 修改是否生效 |
| 返回引用 | double& setValue(int i) | 修改原始数组 |
| 返回值 | double setValue(int i) | 只修改函数内副本 |
指针与引用情话:我希望我们之间是引用的关系,而非指针。(让我们像引用一样,成为彼此生命中不可分割的别名(alias),而不是可以随时指向他人的指针。" 在代码世界里,引用是最深情的绑定方式,正如人间最动人的爱情——不需要多余的语法糖,只需要一次确定的绑定,便是一生的承诺。)哥们玩抽象的。
技术层面:
引用(Reference):一旦初始化就无法改变指向,必须绑定到一个有效的对象,且生命周期与原对象一致。就像"我注定只能是你"。
指针(Pointer):可以随时改变指向,甚至可能悬空(dangling pointer)或指向null,象征关系的不确定性。
九、重载
函数重载
在同一个作用域内,可以声明几个功能类似的同名函数;
C++ 中,函数重载 是指 在同一作用域内,可以有多个同名函数,但它们的参数列表不同(参数的类型、个数或顺序不同)。编译器会根据调用时传入的实参来自动选择匹配的函数。
函数重载的规则:
- 函数名必须相同
- 参数列表必须不同(以下任一):
- 参数个数不同
- 参数类型不同
- 参数顺序不同
- 返回类型可以不同,但不能仅靠返回类型不同来重载
实例代码:
1.参数个数不同
#include <iostream> using namespace std; // 计算两个数的和 int add(int a, int b) { cout << "调用两个参数的 add" << endl; return a + b; } // 计算三个数的和 int add(int a, int b, int c) { cout << "调用三个参数的 add" << endl; return a + b + c; } int main() { cout << add(2, 3) << endl; // 调用 add(int, int) cout << add(2, 3, 4) << endl; // 调用 add(int, int, int) return 0; }2.参数类型不同
#include <iostream> using namespace std; // 整数相加 void printSum(int a, int b) { cout << "整数和: " << a + b << endl; } // 浮点数相加 void printSum(double a, double b) { cout << "浮点数和: " << a + b << endl; } int main() { printSum(3, 5); // 调用 int 版本 printSum(3.5, 6.7); // 调用 double 版本 return 0; }3.参数顺序不同
#include <iostream> using namespace std; // 先 int 后 string void show(int a, string s) { cout << "int: " << a << ", string: " << s << endl; } // 先 string 后 int void show(string s, int a) { cout << "string: " << s << ", int: " << a << endl; } int main() { show(10, "Hello"); // 调用 show(int, string) show("World", 20); // 调用 show(string, int) return 0; }4.结合默认参数(注意歧义)
#include <iostream> using namespace std; void greet(string name) { cout << "Hello, " << name << "!" << endl; } // 如果启用下面这行,会产生调用歧义! // void greet(string name = "Guest") { // cout << "Hi, " << name << "!" << endl; // } //程序会不清楚应该调用哪一个,下面的含有默认的参数,应该输出哪一个 int main() { greet("Alice"); // 如果有两个 greet,编译器不知道选哪个 return 0; }5.重载构造函数(类中的函数重载)
#include <iostream> using namespace std; class Rectangle { private: int width, height; public: // 构造函数1:无参 Rectangle() { width = 1; height = 1; cout << "调用无参构造函数" << endl; } // 构造函数2:一个参数(正方形) Rectangle(int w) { width = w; height = w; cout << "调用单参构造函数" << endl; } // 构造函数3:两个参数 Rectangle(int w, int h) { width = w; height = h; cout << "调用双参构造函数" << endl; } void showArea() { cout << "面积: " << width * height << endl; } }; int main() { Rectangle r1; // 调用 Rectangle() Rectangle r2(5); // 调用 Rectangle(int) Rectangle r3(3, 4); // 调用 Rectangle(int, int) r1.showArea(); r2.showArea(); r3.showArea(); return 0; }十、构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造,构造的是什么:构造成员变量的初始值,内存空间等
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回void。构造函数可用于为某些成员变量设置初始值。
以下是一个实例化
#include <iostream> #include <string> // 1. 定义一个名为 Book 的类 (class) class Book { private: // 私有成员变量,用于存储书的属性 std::string title; std::string author; int pages; bool is_borrowed; public: // 2. 默认构造函数 (Default Constructor) // 当没有提供参数时调用此构造函数 Book() { title = "未知标题"; author = "未知作者"; pages = 0; is_borrowed = false; std::cout << "调用了默认构造函数,创建了一本新书。\n"; } // 3. 带参数的构造函数 (Parameterized Constructor) // 当提供书名和作者时调用此构造函数 Book(const std::string& t, const std::string& a) { title = t; author = a; pages = 0; // 页数默认为0,可以在后续设置 is_borrowed = false; std::cout << "调用了带参数的构造函数,创建了《" << title << "》。\n"; } // 4. 带多个参数的构造函数 (Another Parameterized Constructor) // 当提供书名、作者和页数时调用此构造函数 Book(const std::string& t, const std::string& a, int p) { title = t; author = a; pages = p; is_borrowed = false; std::cout << "调用了带参数的构造函数,创建了《" << title << "》,共 " << pages << " 页。\n"; } // 5. 析构函数 (Destructor) // 对象销毁时自动调用 ~Book() { std::cout << "《" << title << "》这本书被销毁了。\n"; } // 6. 成员函数 (Member Functions) 用于操作对象的状态 void borrow() { if (!is_borrowed) { is_borrowed = true; std::cout << "《" << title << "》已被借出。\n"; } else { std::cout << "《" << title << "》已在借阅中。\n"; } } void returnBook() { if (is_borrowed) { is_borrowed = false; std::cout << "《" << title << "》已归还。\n"; } else { std::cout << "《" << title << "》未被借出。\n"; } } // Const成员函数,用于获取对象状态,不修改对象 void displayInfo() const { std::cout << "书名: " << title << "\n作者: " << author << "\n页数: " << pages << "\n状态: " << (is_borrowed ? "已借出" : "在馆") << "\n"; std::cout << "------------------------\n"; } }; int main() { std::cout << "--- C++ 构造函数与对象实例化演示 ---\n\n"; // 7. 实例化对象 (Instantiation) // 使用默认构造函数创建对象 book1 std::cout << "创建 book1 (使用默认构造函数):\n"; Book book1; // 不需要 'new',对象在栈上分配 book1.displayInfo(); // 使用带两个参数的构造函数创建对象 book2 std::cout << "\n创建 book2 (使用带参数的构造函数):\n"; Book book2("C++ Primer", "Stanley Lippman"); // 自动调用匹配的构造函数 book2.displayInfo(); // 使用带三个参数的构造函数创建对象 book3 std::cout << "\n创建 book3 (使用带参数的构造函数):\n"; Book book3("Effective C++", "Scott Meyers", 250); book3.displayInfo(); // 演示对象方法的使用 std::cout << "\n--- 演示对象方法 ---\n"; book2.borrow(); book2.displayInfo(); book3.borrow(); book3.borrow(); // 尝试再次借出 book3.displayInfo(); book2.returnBook(); book2.displayInfo(); std::cout << "\n--- 程序结束,局部对象将被自动销毁 ---\n"; // 当 main 函数结束时,book1, book2, book3 会自动调用析构函数并被销毁 return 0; } 初始化列表
- 初始化列表是构造函数的一部分,用于在构造函数体执行前初始化成员变量。
- 必须使用初始化列表的场景:const 成员、引用成员、无默认构造函数的类成员。
- 初始化顺序由类中声明的顺序决定,与初始化列表中的顺序无关
一个简单的示例
class MyClass{ private: int a; double b; std::string c; public: //使用初始化列表来初始化字段 Myclass(int x,double y,const std::string& z):a(x),b(y),c(z){ //构造函数体 } };#include <iostream> using namespace std; // 定义一个没有默认构造函数的类 class Helper { public: Helper(int value) { cout << "Helper 构造函数: value = " << value << endl; } }; class MyClass { private: int normalVar; // 普通成员变量 const int constVar; // const 成员变量 int& refVar; // 引用成员变量 Helper helper; // 没有默认构造函数的类成员 public: // 构造函数使用初始化列表 MyClass(int a, int b, int c) : normalVar(a), // 初始化普通变量 constVar(b), // 初始化 const 变量 refVar(c), // 初始化引用变量 helper(c) // 初始化 Helper 对象 { cout << "MyClass 构造函数体" << endl; } void printValues() const { cout << "normalVar: " << normalVar << endl; cout << "constVar: " << constVar << endl; cout << "refVar: " << refVar << endl; } }; int main() { int value = 42; MyClass obj(10, 20, value); // 创建 MyClass 对象 obj.printValues(); // 打印成员变量的值 return 0; }
this关键字
在C++中,this关键字是一个指向调用对象的指针。它在成员函数内部使用,用于引用调用该函数的对象,使用this可以明确指出成员函数正在操作的是哪个对象操作的数据成员。
#include <iostream> #include <string> using namespace std; class Person { private: string name; int age; public: // 构造函数中使用 this 指针区分成员变量和参数 Person(string name, int age) { this->name = name; this->age = age; } // 设置姓名的 setter 方法 void setName(string name) { this->name = name; } // 设置年龄的 setter 方法 void setAge(int age) { this->age = age; } // 返回当前对象的引用,支持链式调用 Person& setInfo(string name, int age) { this->name = name; this->age = age; return *this; } // 打印信息的函数 void printInfo() const { cout << "Name: " << name << ", Age: " << age << endl; } }; int main() { // 创建对象并调用函数 Person p("Alice", 30); p.printInfo(); // 输出: Name: Alice, Age: 30 // 使用链式调用设置信息 p.setAge(31).setName("Bob").printInfo(); // 输出: Name: Bob, Age: 31 // 另一个示例:使用 setInfo 进行链式设置 Person p2("Charlie", 25); p2.setInfo("David", 26).printInfo(); // 输出: Name: David, Age: 26 return 0; }注意这几点:
1.当构造函数的参数名与成员变量名相同时,必须使用 this-> 来明确指定是对对象成员的赋值。
2.链式调用(返回 *this):
return *this;通过返回当前对象的引用,可以实现连续调用多个方法,如:
p.setAge(31).setName("Bob").printInfo();3.printInfo 被声明为 const 方法: 表示该方法不会修改对象的状态,
| 用途 | 示例 |
| 区分成员变量与参数 | this->name = name; |
| 实现链式调用 | return *this; |
| 传递当前对象 | someFunction(this); |
new关键字
在C++中,new关键字用于动态分配内存。它是C++中处理动态内存分配的主要工具之一,允许程序运行时根据需要分配内存。
基本用法
分配单个对象:
使用new可以在堆上动态分配一个对象。例如,new int 会分配一个int类型的空间,并返回指向该空间的指针
int* p = new int; // 分配一个 int 类型的内存空间 int* q = new int(10); // 分配并初始化为 10分配对象数组:
new 也可以用来分配一个对象数组。例如,new int[10]会分配一个包含10个整数的数组。
int* arr = new int[10];分配类对象
MyClass* obj = new MyClass(); // 分配并调用构造函数初始化:
可以在new表达式中使用初始化。用于单个对象,可以使用构造函数的参数。
MyClass* obj = new MyClass(arg1,arg2);
与delete配对使用
使用new分配的内存必须显式地通过delete(对于单个对象)或 delete[ ](对于数组)来释放,以避免内存泄露:
是释放单个对象
delete p;
释放数组
delete[ ] arr;
#include <iostream> #include <new> // 用于 std::nothrow class MyClass { public: MyClass() { std::cout << "MyClass created\n"; } ~MyClass() { std::cout << "MyClass destroyed\n"; } void print() { std::cout << "Hello from MyClass!\n"; } }; int main() { // 1. 分配单个对象 MyClass* obj1 = new MyClass(); obj1->print(); delete obj1; // 2. 分配数组 MyClass* objArray = new MyClass[3]; // 调用 3 次构造函数 delete[] objArray; // 调用 3 次析构函数 // 3. 安全分配(nothrow) int* largeArray = new (std::nothrow) int[1000000000]; if (largeArray) { std::cout << "Allocation succeeded!\n"; delete[] largeArray; } else { std::cerr << "Allocation failed!\n"; } // 4. 内存泄漏示例(注释掉 delete) int* leaky = new int(42); // delete leaky; // 内存泄漏 return 0; }
十一、析构函数
析构函数是C++中的一个特殊的成员函数,他在4对象生命周期结束时被自动调用,用于执行对象销毁前的清理工作。其十分重要,尤其是在涉及动态分配的资源(如内存、文件句柄、网络连接等)的情况下。
- 名称:析构函数的名称由波浪号(~)后跟类名组成,如 ~Myclass()。
- 无返回值和参数:析构函数不接受任何参数,也不返回任何值。
- 自动调用:当对象的生命周期结束时(例如,一个局部对象的作用域结束,或者使用delete删除一个动态分配的对象),析构函数会被自动调用。
- 不可重载:每一个类只能有一个析构函数。
- 继承和多态:如果一个类是多态基类,其析构函数应该是虚的。
class 类名 { public: ~类名(); // 声明析构函数 }; // 定义析构函数 类名::~类名() { // 清理逻辑 }
十二、静态成员
静态成员在C++类中是一个重要的概念,它包括静态成员变量和静态成员函数。
静态成员变量
- 定义:静态成员变量是类的所有对象共享的变量。与普通成员变量相比,无论创建了多少个类的实例,静态成员变量只有一份拷贝。
- 初始化: