面向过程:关注事件的逻辑性流程
面向对象:关注事件出现的对象
面向对象更高级于面向过程
面向对象的三大特性:封装、继承、多态。
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
注:计算对象的大小只计算成员变量大小总和,而不计算函数。
class
的默认访问权限为private,struct
为public(因为struct要兼容C)封装的定义:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。
对于stack的实现C语言与C++的区别:
从底层来说没有区别。
封装的实现:通过访问限定符的变换,将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
在类中,成员变量仅仅是被声明而没有被定义;
当我们在其他地方使用这个变量的时候,才会对其进行定义(也就是实例化)
tips:从这里也可以得知声明和定义的区别:声明仅仅是一个抽象化的规定;而定义才是将其实例化,将其变成实际的东西
内存对齐规则:牺牲空间提高性能
// 类中既有成员变量,又有成员函数 占成员变量的字节和,并且遵循内存对齐规则
class A1 {
public:
void f1(){}
private:
int _a;
};
// 类中仅有成员函数 占一个字节,表示成员函数
class A2 {
public:
void f2() {}
};
// 类中什么都没有---空类 占一个字节,用于占位
class A3
{};
调用同一个函数,但是结果不一样——使用了隐含的this指针。
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数this,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。
只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成。
this指针的特性
类类型* const
,所以成员函数无法给this指针赋值构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,完成对象的初始化。并且在对象整个生命周期内只调用一次。注意:构造函数的主要任务并不是开空间创建对象,而是初始化对象。
void
)。
1 默认构造函数(无参数)
#include <iostream>
using namespace std;
class Person {
public:
string name;
// 默认构造函数(无参数)
Person() {
cout << "默认构造函数被调用!" << endl;
name = "Unknown";
}
void show() { cout << "Name: " << name << endl; }
};
int main() {
Person p; // 自动调用构造函数
p.show();
return 0;
}
输出
默认构造函数被调用!//并未显式调用,但是却依旧打印------说明自动调用
Name: Unknown
📌 特点:
2.带参数的构造函数
class Person {
public:
string name;
int age;
// 带参数的构造函数
Person(string n, int a) {
name = n;
age = a;
}
void show() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main() {
Person p("Alice", 25); // 传入参数
p.show();
return 0;
}
📌 特点:
Person p;
将报错)。构造函数相当于:
Init()
构造函数的调用和普通函数也不一样。
通常构造函数都需要自己写
是 C++ 中用于创建一个新对象,并用已有对象进行初始化的特殊构造函数。
特点:
ClassName(const ClassName& other);
const &
(该形参是对本类类型对象的引用),否则会导致无限递归调用。C++ 在以下情况会自动调用拷贝构造函数:
对象初始化
ClassName obj1;
ClassName obj2 = obj1; // 调用拷贝构造函数
对象按值传递
void func(ClassName obj); // 传递参数时调用拷贝构造
对象按值返回
ClassName func() { return obj; } // 返回对象时调用拷贝构造
用已有对象创建新对象
ClassName obj1;
ClassName obj2(obj1); // 调用拷贝构造函数
C++ 如果不定义拷贝构造函数,编译器会自动生成一个默认版本,进行浅拷贝(Shallow Copy)。
3.1 默认拷贝构造(编译器自动生成)
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
Person(string n, int a) : name(n), age(a) {} // 普通构造
void show() { cout << "Name: " << name << ", Age: " << age << endl; }
};
int main() {
Person p1("Alice", 25);
Person p2 = p1; // 触发默认拷贝构造
p2.show(); // Name: Alice, Age: 25
}
📌 特点
3.2 浅拷贝的缺陷
当类包含指针成员时,默认拷贝构造会复制指针地址,而不是复制指针指向的内容,导致多个对象共享同一块内存,析构时会二次释放,导致程序崩溃。
class Person {
public:
char* name;
Person(const char* n) {
name = new char[strlen(n) + 1]; // 动态分配内存
strcpy(name, n);
}
~Person() { delete[] name; } // 释放内存
};
int main() {
Person p1("Alice");
Person p2 = p1; // ❌ 默认拷贝构造,导致 p1 和 p2 指向同一块内存
return 0;
} // ❌ p1 和 p2 都调用析构函数,导致 double free 错误
📌 问题
name
内存,当 p1
和 p2
析构时,会两次释放相同的内存,引发 “double free or corruption” 错误。✅ 解决方案:手动实现深拷贝!
深拷贝(Deep Copy) 会重新分配内存,并复制指针指向的内容,保证每个对象都有自己独立的资源。
如果类涉及动态分配内存,建议实现深拷贝,以确保内存安全!
4.1 实现深拷贝
#include <iostream>
#include <cstring>
using namespace std;
class Person {
public:
char* name;
// 构造函数
Person(const char* n) {
name = new char[strlen(n) + 1]; // 分配新内存
strcpy(name, n);
}
// 自定义拷贝构造(深拷贝)
Person(const Person& p) {
cout << "拷贝构造函数被调用!" << endl;
name = new char[strlen(p.name) + 1]; // 重新分配内存
strcpy(name, p.name);
}
// 析构函数
~Person() { delete[] name; }
void show() { cout << "Name: " << name << endl; }
};
int main() {
Person p1("Alice");
Person p2 = p1; // 触发拷贝构造(深拷贝)
p2.show();
}
📌 特点
name = new char[strlen(p.name) + 1];
重新分配内存,避免共享同一块地址。double free
错误。在某些情况下,我们不希望对象被拷贝,可以显式删除拷贝构造函数(C++11)。
class Person {
public:
Person(const Person&) = delete; // 禁止拷贝
};
📌 作用
拷贝构造和赋值运算符的区别:
对比项 | 拷贝构造函数 | 赋值运算符 = |
---|---|---|
作用 | 创建新对象并初始化 | 复制已有对象的值 |
触发时机 | ClassName obj2 = obj1; | obj2 = obj1;(对象已存在) |
默认行为 | 浅拷贝 | 浅拷贝 |
适用场景 | 对象初始化 | 对象已存在,需赋新值 |
示例
class Person {
public:
string name;
// 拷贝构造函数
Person(const Person& p) { name = p.name; }
// 赋值运算符
Person& operator=(const Person& p) {
if (this == &p) return *this; // 避免自赋值
name = p.name;
return *this;
}
};
int main() {
Person p1("Alice");
Person p2 = p1; // 调用拷贝构造
p2 = p1; // 调用赋值运算符
}
C++11 引入移动构造函数,可以避免拷贝,提高性能。
class Person {
public:
string name;
// 移动构造函数
Person(Person&& p) noexcept : name(move(p.name)) {
cout << "移动构造函数被调用!" << endl;
}
};
📌 区别
Person(const Person&)
复制数据。Person(Person&&)
直接转移资源,避免不必要的拷贝。构造函数的反操作,在对象销毁时自动调用。完成资源清理工作(功能与构造函数相反)。
格式:~类名()
,无参数、无返回值。
若未显式定义,系统会自动生成默认的析构函数
主要用于释放动态分配的资源(如 new
)。在函数生命周期结束时,析构函数自动调用
析构函数无重载,所以每个类只有一个析构函数。
class Person {
public:
Person() { cout << "构造函数" << endl; }
~Person() { cout << "析构函数" << endl; }
};
int main() {
Person p; // 作用域结束,调用析构函数
}
相当于:
注意:如果类中没有动态申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date
类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack
类。
构造函数和析构函数用于简化代码;同时这两个函数的出现使得代码更具有安全性和便携性。
用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
#mermaid-svg-lYRtb51Htzp7Khfa {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-lYRtb51Htzp7Khfa .error-icon{fill:#552222;}#mermaid-svg-lYRtb51Htzp7Khfa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lYRtb51Htzp7Khfa .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-lYRtb51Htzp7Khfa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lYRtb51Htzp7Khfa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lYRtb51Htzp7Khfa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lYRtb51Htzp7Khfa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lYRtb51Htzp7Khfa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lYRtb51Htzp7Khfa .marker.cross{stroke:#333333;}#mermaid-svg-lYRtb51Htzp7Khfa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lYRtb51Htzp7Khfa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lYRtb51Htzp7Khfa .cluster-label text{fill:#333;}#mermaid-svg-lYRtb51Htzp7Khfa .cluster-label span{color:#333;}#mermaid-svg-lYRtb51Htzp7Khfa .label text,#mermaid-svg-lYRtb51Htzp7Khfa span{fill:#333;color:#333;}#mermaid-svg-lYRtb51Htzp7Khfa .node rect,#mermaid-svg-lYRtb51Htzp7Khfa .node circle,#mermaid-svg-lYRtb51Htzp7Khfa .node ellipse,#mermaid-svg-lYRtb51Htzp7Khfa .node polygon,#mermaid-svg-lYRtb51Htzp7Khfa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lYRtb51Htzp7Khfa .node .label{text-align:center;}#mermaid-svg-lYRtb51Htzp7Khfa .node.clickable{cursor:pointer;}#mermaid-svg-lYRtb51Htzp7Khfa .arrowheadPath{fill:#333333;}#mermaid-svg-lYRtb51Htzp7Khfa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lYRtb51Htzp7Khfa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lYRtb51Htzp7Khfa .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-lYRtb51Htzp7Khfa .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-lYRtb51Htzp7Khfa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lYRtb51Htzp7Khfa .cluster text{fill:#333;}#mermaid-svg-lYRtb51Htzp7Khfa .cluster span{color:#333;}#mermaid-svg-lYRtb51Htzp7Khfa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lYRtb51Htzp7Khfa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
参数不足
参数足够
读取失败
读取成功
创建失败
创建成功
分配失败
分配成功
开始
检查命令行参数
提示用户并退出
读取TXT文件
提示错误并退出
转换TXT数据
创建BMP文件
提示错误并退出
写入文件头
写入信息头
分配内存
提示错误并退出
设置图像数据
写入图像数据
释放内存
关闭文件
提示生成成功
结束
C++为了增强代码的可读性引入运算符重载。在介绍赋值运算符重载之前我们先来介绍运算符重载。
普通的运算符是类似于“=”、“+”等带有运算含义的符号,运算符重载就是具有特殊函数名的函数,这些函数名都会接需要重载的运算符。
格式如下:
返回值类型 operator操作符(参数列表)
举个例子更好理解:
bool operator==(const Date& d1, const Date& d2)
重载了“==”这个运算符,使它带有额外的含义。
+
这个运算符,它依旧起着类似于1+1=2
的作用,这是不会改变的。
这其实也体现了重载的意义:不改变原先的用法,而是重新新增一种用法。
operator@
,这是不被允许的。
.*
、::
、 sizeof
、 ?:
、 .
this
。
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test ()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
}
(1)前置 ++
*this
进行修改ClassName&
),支持链式调用cpp复制编辑class Counter {
private:
int value;
public:
Counter(int v) : value(v) {}
// 前置++ 重载
Counter& operator++() {
++value; // 直接修改当前对象
return *this; // 返回自身引用
}
void show() const {
std::cout << "Value: " << value << std::endl;
}
};
int main() {
Counter c(5);
++c; // 调用前置++
c.show(); // Value: 6
}
(2)后置 ++
int
作为参数(用于区分前置 ++
)+1
cpp复制编辑class Counter {
private:
int value;
public:
Counter(int v) : value(v) {}
// 后置++ 重载
Counter operator++(int) {
Counter temp = *this; // 先保存旧值
++value; // 然后自增
return temp; // 返回旧对象
}
void show() const {
std::cout << "Value: " << value << std::endl;
}
};
int main() {
Counter c(5);
Counter d = c++; // 先返回旧对象,再自增
c.show(); // Value: 6
d.show(); // Value: 5 (因为 d 赋值的是旧对象)
}
所以只需要记住一句话:前置++无需额外的参数,而后置++需要一个int
参数来用于与前置++区分。
赋值运算符重载 (operator=
) 是 特殊的运算符重载
我们知道,在C++中,=
这个运算符的含义是赋值,所以这也是它名字的由来。
那么对于赋值运算符的重载,会有哪些好处呢?
默认情况下,编译器会为类提供一个 默认的赋值运算符=,用于进行对象的浅拷贝(即逐成员赋值);
但是在某些情况下(如涉及动态内存分配),需要 重载赋值运算符 以进行深拷贝,避免浅拷贝带来的问题(如双重释放)。
赋值运算符格式如下:
class ClassName {
public:
ClassName& operator=(const ClassName& other) {
if (this == &other) {
return *this; // 避免自赋值
}
// 清理已有资源(如果有)
// 复制 `other` 的资源
return *this; // 返回自身引用
}
};
参数类型:const ClassName&
,避免不必要的拷贝,传递引用可以提高传参效率
返回类型:返回 ClassName&
,以支持连续赋值,如 a = b = c;
检查自赋值:避免 obj = obj;
这种情况导致资源被错误释放
释放旧资源(如果涉及动态内存)
深拷贝(如果类涉及指针资源)
赋值运算符只能重载成类的成员函数,而不能重载成全员函数。
对于这一点,我们看一个例子:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
if (&left != &right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
}
return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员
原因就是用户在类外实现的一个全局的赋值运算符重载,会和类中的默认的赋值运算符冲突。
const
成员(常成员)在 C++ 中,const
关键字可以用于类的 数据成员、成员函数 和 对象,主要目的是防止修改、提高代码安全性。
const
数据成员(常数据成员)const
数据成员在对象构造时必须初始化,之后不能修改。#include <iostream>
class Test {
private:
const int x; // 常数据成员
public:
// 必须在初始化列表中赋值
Test(int val) : x(val) {}
void show() {
std::cout << "x = " << x << std::endl;
}
};
int main() {
Test t(10);
t.show(); // x = 10
// t.x = 20; // ❌ 错误:const 成员不能修改
}
📌 关键点
const int x;
不能在构造函数内赋值,只能在 初始化列表 中(也就是例子中的Test t(10)
)赋值。const
成员函数(常成员函数)const
成员函数 不能修改对象的成员变量(除了 mutable
修饰的变量)。
语法:在函数声明和定义后加 const
关键字:
返回类型 函数名(参数) const;
常成员函数 只能调用其他的 const
成员函数,不能调用非 const
的成员函数。
#include <iostream>
class Test {
private:
int value;
public:
Test(int v) : value(v) {}
// const 成员函数
void show() const {
std::cout << "Value: " << value << std::endl;
}
// 非 const 成员函数(可以修改成员变量)
void setValue(int v) {
value = v;
}
};
int main() {
const Test t(100); // 常对象
t.show(); // ✅ OK: show() 是 const 成员函数
// t.setValue(200); // ❌ 错误: 不能调用非 const 成员函数
}
📌 关键点
void show() const;
保证函数不会修改成员变量。const Test t(100);
只能调用 const
成员函数,不能调用 setValue()
。const
对象(常对象)const
对象只能调用 const
成员函数,不能调用非 const
的成员函数。const
对象的成员变量。#include <iostream>
class Test {
private:
int data;
public:
Test(int v) : data(v) {}
void show() const {
std::cout << "Data: " << data << std::endl;
}
void setData(int v) {
data = v;
}
};
int main() {
const Test t(42); // 常对象
t.show(); // ✅ OK:可以调用 const 成员函数
// t.setData(100); // ❌ 错误:不能调用非 const 成员函数
}
📌 关键点
const Test t(42);
不能调用 setData()
,但可以调用 show()
。const
对象 只能调用 const
成员函数。mutable
关键字mutable
允许 const
成员函数 修改特定成员变量。mutable
可变成员#include <iostream>
class Logger {
private:
mutable int accessCount; // 可变成员变量
public:
Logger() : accessCount(0) {}
void log() const {
accessCount++; // ✅ 在 const 成员函数中修改
std::cout << "Access count: " << accessCount << std::endl;
}
};
int main() {
const Logger logger; // 常对象
logger.log(); // ✅ 即使是 const 对象,也能修改 mutable 变量
}
📌 关键点
mutable int accessCount;
允许在 const
成员函数中修改。总的来说, const
提高了安全性,防止意外修改数据,是 C++ 代码的好习惯。
初始化列表是构造函数的一种特殊形式,它用于在构造函数体之前初始化成员变量。
我们需要注意,在普通的构造函数体赋值中,我们不过是给了成员变量一个合适的初始值,而并非叫做初始化。
因为初始化只能初始化一次。而赋值可以多次赋值。
在某些应用场景下,额外的赋值会降低调用的效率,那么我们直接将变量初始化,就可以避免不必要的默认构造和赋值,从而提高效率。
那么我们就需要一种方式来进行真正的初始化,初始化列表就出现了。
初始化列表不会先调用默认构造函数再赋值,直接初始化,提高性能。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
如下:
ClassName(参数列表): 成员1(初始化值),成员2(初始化值),...{...}
举例:
class Date
{
public:
Date(int year,int month,int day):
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意:
每个成员变量在初始化列表中只能出现一次,这也刚好对应了初始化只能初始化一次;
以下成员必须要在初始化列表进行初始化:
1.引用成员变量
2.const成员变量
3.自定义类型成员(且该类没有默认构造函数)
然而,实际上除了以上成员,我们应该尽可能使用初始化列表,对于任何成员。
初始化列表的执行顺序按照成员变量的声明顺序,而不是初始化列表中的书写顺序。
看一下例子:
class Test {
private:
int a;
int b;
public:
Test() : b(20), a(10) { // a 先声明,但在初始化列表中书写在b后面
cout << "a: " << a << ", b: " << b << endl;
}
};
打印出来是:
a: 0, b: 20
因为成员变量的初始化顺序是按照声明顺序(a
先声明,所以a
先初始化),而不是初始化列表的顺序。
下面对三种情况进行分别举例。
const
成员变量
class Example {
private:
const int value;
public:
Example(int v): value(v) {}
};
引用成员变量:引用成员变量不能再构造函数体内赋值
class Example{
private:
int &ref;
public:
Example(int &r): ref(r) {}
};
自定义成员变量(且该类没有默认构造函数)
class MyClass {
public:
// 只有带参数的构造函数,没有默认构造函数(即没有 MyClass())
MyClass(int value) {
// 构造函数逻辑
}
};
class Container{
private:
MyClass obj; //成员变量,必须初始化
public:
Container(int v): obj(v) {}
};
构造函数不仅可以构造与初始化一个对象,
对于接受单个参数的构造函数,还可以起到类型转换的作用。
先理解接受单个参数的构造函数的概念,它有三种情况:
然后对于其类型转换的作用,这里举一个例子,来看看使用不当可能导致什么结果
#include <iostream>
using namespace std;
class Example {
public:
Example(int value) { cout << "Constructor called with value: " << value << endl; }
};
int main() {
Example ex1 = 10; // ❶ 允许隐式转换
func(20); // ❷ 允许隐式转换,将 20 变为 Example 类型
return 0;
}
输出结果:
Constructor called with value: 10 //触发隐式转换,使得 10 变成了Example(10)
Constructor called with value: 20 //func(20); 本意是传递一个 Example 类型对象,但 20 被隐式转换成了 Example(20),导致意外的构造函数调用,可能引起潜在的逻辑错误。
那么如果我们加上Explicit
关键字,就可以规避这种隐式转换。
#include <iostream>
using namespace std;
class Example {
public:
explicit Example(int value) { cout << "Constructor called with value: " << value << endl; }
};
void func(Example ex) { cout << "Function executed!" << endl; }
int main() {
Example ex1 = 10; // ❌ 错误:explicit 禁止隐式转换
Example ex2(10); // ✅ 正确:直接初始化
func(20); // ❌ 错误:不能隐式转换 int → Example
func(Example(20)); // ✅ 正确:显式转换
return 0;
}
在例子中我们看到,既然不能隐式转换,那么就只能显示转换了,这是可行的。
对于错误的部分,会报错:
error: no viable conversion from 'int' to 'Example'
Example ex1 = 10; // ❌ 这里会报错
用Explicit
修饰构造函数,将会禁止构造函数的隐式转换。
声明为static
的类成员称为类的静态成员;
用static
修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化;
用static
修饰的成员函数,称之为静态成员函数。
静态成员为所有类对象共享,不属于某个具体对象,存放在静态区;
通常来说,静态成员变量会在类外定义和初始化(因为静态成员变量是类级别的,不属于任何一个实例),但是在类内可以进行声明,不为它分配存储空间。
#include <iostream>
class Example {
public:
static int count; // 仅声明,添加static关键字,不能在类内直接初始化
};
// 在类外定义和初始化静态变量,不添加static关键字
int Example::count = 0;
int main() {
std::cout << "Count: " << Example::count << std::endl;
return 0;
}
属于整个类,而不是某个对象。
可以直接通过类名调用(ClassName::Function()
)。
不能访问非静态成员变量或成员函数(因为它不依赖具体对象,没有this
指针)。
但是, 非静态可以调用静态。
#include <iostream>
class Example {
private:
static int count;
int id;
public:
Example(int x) : id(x) { count++; }
static void showCount() {
std::cout << "Total objects: " << count << std::endl;
// std::cout << "ID: " << id; // ❌ 错误,静态函数不能访问非静态成员
}
};
int Example::count = 0;
int main() {
Example obj1(1), obj2(2);
Example::showCount(); // 输出 2
return 0;
}
匿名对象是 没有显式变量名的临时对象,在 C++ 中,它通常用于简化代码、减少不必要的变量声明,并提高程序的效率。
匿名对象具有以下特点:
#include <iostream>
class Number {
public:
Number(int x) { std::cout << "Number: " << x << " 构造\n"; }
~Number() { std::cout << "Number 析构\n"; }
};
int main() {
// 不使用匿名对象
Number n1(10);
// 使用匿名对象
Number(20); // 创建后立即销毁
return 0;
}
const
的,即鉴于其极短的生命周期,编译器对其进行了常性优化,使得:
无法修改匿名对象、只能调用const
成员函数。
针对匿名对象的生命周期,可以普遍理解为就在当前行;
有名对象的生命周期在当前函数局部域;
如果想要延长匿名对象的生命周期,可以使用const A&
进行绑定,A是它对应的类。从而使得它的生命周期延长到当前局部函数域。