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++类中是一个重要的概念,它包括静态成员变量和静态成员函数。
静态成员变量
- 定义:静态成员变量是类的所有对象共享的变量。与普通成员变量相比,无论创建了多少个类的实例,静态成员变量只有一份拷贝。
- 初始化:
更多推荐

所有评论(0)