1.类的6个默认成员函数:
在C++中,即使一个类没有定义任何成员或成员函数,编译器仍会为其生成以下6个默认成员函数。下面是对这些默认成员函数的简易分析和代码示例。
默认构造函数在创建对象时被调用。如果类中没有定义任何构造函数,编译器会自动生成一个默认的无参构造函数。
class MyClass {
// 编译器会生成一个默认构造函数
};
MyClass obj; // 调用默认构造函数
析构函数在对象被销毁时调用。编译器会生成一个默认的析构函数来清理资源。
class MyClass {
// 编译器会生成一个默认析构函数
};
{
MyClass obj; // 析构函数在作用域结束时被调用
}
拷贝构造函数用于创建一个新的对象作为现有对象的副本。如果没有定义拷贝构造函数,编译器会生成一个默认的。
class MyClass {
// 编译器会生成一个默认的拷贝构造函数
};
MyClass obj1;
MyClass obj2 = obj1; // 调用默认拷贝构造函数
拷贝赋值运算符用于将一个对象的值赋给另一个对象。如果没有定义拷贝赋值运算符,编译器会生成一个默认的。
class MyClass {
// 编译器会生成一个默认的拷贝赋值运算符
};
MyClass obj1;
MyClass obj2;
obj2 = obj1; // 调用默认拷贝赋值运算符
移动构造函数在C++11中引入,用于从一个临时对象中“偷取”资源。如果没有定义移动构造函数,编译器会生成一个默认的。
class MyClass {
// 编译器会生成一个默认的移动构造函数
};
MyClass obj1;
MyClass obj2 = std::move(obj1); // 调用默认移动构造函数
构造函数是C++中的一个重要概念,它使对象在创建时自动初始化。以下是对构造函数的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。
构造函数是一个特殊的成员函数,名字与类名相同。当创建类类型对象时,编译器会自动调用构造函数,以保证每个数据成员都有一个合适的初始值,并且在对象的整个生命周期内只调用一次。
假设我们有一个 Date
类,需要在创建对象时设置日期信息。
class Date {
public:
Date(int year, int month, int day) { // 构造函数
_year = year;
_month = month;
_day = day;
}
void display() {
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
通过这个构造函数,创建对象时可以直接设置日期信息:
Date today(2024, 5, 28); // 调用构造函数
today.display(); // 输出: 2024-5-28
构造函数具有以下特性:
class Date {
public:
// 默认构造函数
Date() : _year(0), _month(0), _day(0) {}
// 带参数的构造函数
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
void display() {
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
Date defaultDate; // 调用默认构造函数
Date specificDate(2024, 5, 28); // 调用带参数的构造函数
defaultDate.display(); // 输出: 0-0-0
specificDate.display(); // 输出: 2024-5-28
如果没有显式定义构造函数,编译器会自动生成一个无参的默认构造函数。这个默认构造函数对内置类型成员变量不进行初始化,而对自定义类型成员变量会调用它们的默认构造函数。
class MyClass {
public:
int a; // 内置类型,不会初始化
std::string b; // 自定义类型,会调用其默认构造函数
};
int main() {
MyClass obj;
std::cout << "a: " << obj.a << ", b: " << obj.b << std::endl; // a: 随机值, b: 空字符串
return 0;
}
C++11允许在类定义时为内置类型成员变量提供默认值:
class MyClass {
public:
int a = 0; // 内置类型,提供默认值
std::string b; // 自定义类型
};
int main() {
MyClass obj;
std::cout << "a: " << obj.a << ", b: " << obj.b << std::endl; // a: 0, b: 空字符串
return 0;
}
构造函数是用于初始化对象的特殊成员函数,其名称与类名相同且无返回值。构造函数可以重载,使得对象在不同的情况下被初始化。如果没有定义构造函数,编译器会生成一个默认的构造函数,但它对内置类型成员变量不进行初始化。C++11引入了在类定义时为内置类型成员变量提供默认值的功能,从而增强了默认构造函数的实用性。
析构函数是C++中的一个重要概念,它使对象在销毁时能自动清理资源。以下是对析构函数的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。
析构函数与构造函数功能相反,不是完成对对象本身的销毁,而是用于清理对象中的资源。当对象的生命周期结束时,C++编译器会自动调用析构函数。
假设我们有一个 Date
类,不需要特别的资源管理,因此可以使用编译器生成的默认析构函数。
class Date {
public:
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
void display() {
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
~Date() {
// 编译器会自动调用这个析构函数
std::cout << "Date对象被销毁: " << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
通过这个析构函数,可以在对象销毁时自动打印一条消息:
int main() {
Date today(2024, 5, 28);
today.display();
return 0;
}
// 输出:
// 2024-5-28
// Date对象被销毁: 2024-5-28
析构函数具有以下特性:
~
。class MyClass {
public:
MyClass() {
std::cout << "MyClass对象创建" << std::endl;
}
~MyClass() {
std::cout << "MyClass对象销毁" << std::endl;
}
};
int main() {
MyClass obj;
return 0;
}
// 输出:
// MyClass对象创建
// MyClass对象销毁
在上述代码中,当对象 obj
的生命周期结束时,编译器会自动调用析构函数。
当类中有资源需要管理时,例如动态内存,必须显式定义析构函数以防止资源泄漏。
class Stack {
public:
Stack(int size) {
_size = size;
_data = new int[size]; // 动态分配内存
std::cout << "Stack对象创建,分配内存" << std::endl;
}
~Stack() {
delete[] _data; // 释放内存
std::cout << "Stack对象销毁,释放内存" << std::endl;
}
private:
int _size;
int* _data;
};
int main() {
Stack stack(10);
return 0;
}
// 输出:
// Stack对象创建,分配内存
// Stack对象销毁,释放内存
析构函数是用于清理对象资源的特殊成员函数,其名称是在类名前加上字符 ~
,且无参数和返回值。一个类只能有一个析构函数,不能重载。当对象的生命周期结束时,C++编译器会自动调用析构函数。对于没有资源需要管理的类,可以使用编译器生成的默认析构函数;对于需要管理资源的类,必须显式定义析构函数以防止资源泄漏。
拷贝构造函数允许创建一个与已存在对象完全相同的新对象。以下是对拷贝构造函数的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。
在C++中,拷贝构造函数是一个特殊的构造函数,用于创建一个与已有对象相同的新对象。它的参数是对本类类型对象的引用,通常用 const
修饰。
假设我们有一个 Date
类,通过拷贝构造函数可以创建一个与已存在对象相同的新对象。
class Date {
public:
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
// 拷贝构造函数
Date(const Date& other) : _year(other._year), _month(other._month), _day(other._day) {
std::cout << "调用拷贝构造函数" << std::endl;
}
void display() const {
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date date1(2024, 5, 28);
Date date2 = date1; // 调用拷贝构造函数
date2.display();
return 0;
}
// 输出:
// 调用拷贝构造函数
// 2024-5-28
拷贝构造函数具有以下特征:
class Stack {
public:
Stack(int size) : _size(size), _data(new int[size]) {
std::cout << "Stack对象创建,分配内存" << std::endl;
}
// 拷贝构造函数实现深拷贝
Stack(const Stack& other) : _size(other._size), _data(new int[other._size]) {
std::copy(other._data, other._data + other._size, _data);
std::cout << "调用拷贝构造函数,进行深拷贝" << std::endl;
}
~Stack() {
delete[] _data;
std::cout << "Stack对象销毁,释放内存" << std::endl;
}
private:
int _size;
int* _data;
};
int main() {
Stack stack1(10);
Stack stack2 = stack1; // 调用拷贝构造函数
return 0;
}
// 输出:
// Stack对象创建,分配内存
// 调用拷贝构造函数,进行深拷贝
// Stack对象销毁,释放内存
// Stack对象销毁,释放内存
为了提高程序效率,一般对象传参时尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
拷贝构造函数是用于创建一个与已有对象相同的新对象的特殊构造函数。它的参数是对本类类型对象的引用,且无返回值。若未显式定义,编译器会生成默认的拷贝构造函数,对内置类型进行浅拷贝,对自定义类型调用其拷贝构造函数完成拷贝。对于涉及资源管理的类,显式定义拷贝构造函数以实现深拷贝是必要的,以防止资源泄漏。
赋值运算符重载是C++中运算符重载的一种形式,它允许我们自定义类对象之间的赋值行为。以下是对赋值运算符重载的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。
运算符重载是C++引入的一种机制,用于增强代码的可读性。运算符重载的函数具有特殊的名字,并且具有返回值类型、函数名字以及参数列表,其返回值类型和参数列表与普通的函数类似。函数名字为关键字 operator
后面接需要重载的运算符符号。
赋值运算符重载是一种常见的运算符重载形式,用于定义类对象之间的赋值操作。
const T&
,传递引用可以提高传参效率。T&
,返回引用可以提高返回效率,并支持连续赋值。*this
:符合连续赋值的含义。假设我们有一个 Stack
类,通过赋值运算符重载可以定义对象之间的赋值操作。
class Stack {
public:
Stack(int size) : _size(size), _data(new int[size]) {
std::cout << "Stack对象创建,分配内存" << std::endl;
}
// 拷贝构造函数实现深拷贝
Stack(const Stack& other) : _size(other._size), _data(new int[other._size]) {
std::copy(other._data, other._data + other._size, _data);
std::cout << "调用拷贝构造函数,进行深拷贝" << std::endl;
}
// 赋值运算符重载
Stack& operator=(const Stack& other) {
if (this == &other) {
return *this; // 检测自我赋值
}
delete[] _data; // 释放旧内存
_size = other._size;
_data = new int[_size];
std::copy(other._data, other._data + _size, _data);
std::cout << "调用赋值运算符重载,进行深拷贝" << std::endl;
return *this; // 支持连续赋值
}
~Stack() {
delete[] _data;
std::cout << "Stack对象销毁,释放内存" << std::endl;
}
private:
int _size;
int* _data;
};
int main() {
Stack stack1(10);
Stack stack2(5);
stack2 = stack1; // 调用赋值运算符重载
return 0;
}
// 输出:
// Stack对象创建,分配内存
// Stack对象创建,分配内存
// Stack对象销毁,释放内存
// 调用赋值运算符重载,进行深拷贝
// Stack对象销毁,释放内存
前置和后置自增运算符也可以重载。它们分别表示在变量本身修改之前和之后返回值。
class Counter {
public:
Counter(int value = 0) : _value(value) {}
// 前置++
Counter& operator++() {
++_value;
return *this;
}
// 后置++
Counter operator++(int) {
Counter temp = *this;
++_value;
return temp;
}
void display() const {
std::cout << "Counter: " << _value << std::endl;
}
private:
int _value;
};
int main() {
Counter c(5);
++c; // 前置++
c.display(); // Counter: 6
c++; // 后置++
c.display(); // Counter: 7
return 0;
}
赋值运算符重载允许自定义类对象之间的赋值行为。它的参数类型通常是 const T&
,返回值类型是 T&
,并且需要检测自我赋值和返回 *this
以支持连续赋值。赋值运算符只能重载成类的成员函数,并且如果类涉及资源管理,则必须显式实现赋值运算符重载。前置和后置自增运算符也可以重载,以实现不同的自增行为。
构造函数、拷贝构造函数、赋值运算符重载、析构函数以及基本的成员函数,用于表示和操作日期。
首先,我们定义类的成员变量和基本的构造函数。
#include <iostream>
class Date {
public:
// 带参数的构造函数
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {
std::cout << "调用带参数的构造函数" << std::endl;
}
// 默认构造函数
Date() : _year(0), _month(0), _day(0) {
std::cout << "调用默认构造函数" << std::endl;
}
// 拷贝构造函数
Date(const Date& other) : _year(other._year), _month(other._month), _day(other._day) {
std::cout << "调用拷贝构造函数" << std::endl;
}
// 赋值运算符重载
Date& operator=(const Date& other) {
if (this != &other) { // 检测自我赋值
_year = other._year;
_month = other._month;
_day = other._day;
std::cout << "调用赋值运算符重载" << std::endl;
}
return *this; // 支持连续赋值
}
// 析构函数
~Date() {
std::cout << "调用析构函数" << std::endl;
}
// 显示日期
void display() const {
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
// 创建日期对象并显示
Date date1(2024, 5, 28);
date1.display();
// 使用拷贝构造函数创建新对象并显示
Date date2 = date1;
date2.display();
// 使用赋值运算符重载并显示
Date date3;
date3 = date1;
date3.display();
return 0;
}
构造函数用于初始化对象的成员变量。带参数的构造函数可以接受初始化参数,而默认构造函数则不接受参数。
// 带参数的构造函数
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {
std::cout << "调用带参数的构造函数" << std::endl;
}
// 默认构造函数
Date() : _year(0), _month(0), _day(0) {
std::cout << "调用默认构造函数" << std::endl;
}
当我们创建对象时,构造函数会被调用。例如:
Date date1(2024, 5, 28); // 调用带参数的构造函数
Date date3; // 调用默认构造函数
拷贝构造函数用于创建一个新的对象作为已有对象的副本。它接受一个常量引用作为参数。
// 拷贝构造函数
Date(const Date& other) : _year(other._year), _month(other._month), _day(other._day) {
std::cout << "调用拷贝构造函数" << std::endl;
}
当我们用一个已有对象初始化新对象时,拷贝构造函数会被调用。例如:
Date date2 = date1; // 调用拷贝构造函数
赋值运算符重载用于定义对象之间的赋值操作。它返回一个对当前对象的引用,以支持连续赋值。
// 赋值运算符重载
Date& operator=(const Date& other) {
if (this != &other) { // 检测自我赋值
_year = other._year;
_month = other._month;
_day = other._day;
std::cout << "调用赋值运算符重载" << std::endl;
}
return *this; // 支持连续赋值
}
当我们将一个对象赋值给另一个对象时,赋值运算符重载会被调用。例如:
date3 = date1; // 调用赋值运算符重载
析构函数用于在对象生命周期结束时清理资源。
// 析构函数
~Date() {
std::cout << "调用析构函数" << std::endl;
}
当对象超出其作用域时,析构函数会被调用。例如:
{
Date tempDate; // 创建临时对象
} // tempDate 超出作用域,调用析构函数
display函数用于输出日期。
// 显示日期
void display() const {
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
运行上面的代码,将会输出以下内容:
调用带参数的构造函数
2024-5-28
调用拷贝构造函数
2024-5-28
调用默认构造函数
调用赋值运算符重载
2024-5-28
调用析构函数
调用析构函数
调用析构函数
const
成员函数const
成员函数是指被const
修饰的成员函数,这种修饰实际作用于该成员函数的隐含this
指针,表明在该成员函数中不能对类的任何成员进行修改。通过以下问题的解答,我们可以深入理解const
成员函数的行为。
const
对象可以调用非const
成员函数吗?不可以。因为非const
成员函数可能会修改对象的状态,而const
对象保证其状态不会被改变。
const
对象可以调用const
成员函数吗?可以。const
成员函数不会修改对象的状态,因此非const
对象可以调用它。
const
成员函数内可以调用其它的非const
成员函数吗?不可以。因为非const
成员函数可能会修改对象的状态,而在const
成员函数内不能修改对象的状态。
const
成员函数内可以调用其它的const
成员函数吗?可以。非const
成员函数可以调用const
成员函数,因为const
成员函数不会修改对象的状态。
下面我们通过一个示例来说明这些概念。
#include <iostream>
class MyClass {
public:
// 构造函数
MyClass(int value) : _value(value) {}
// 非const成员函数
void setValue(int value) {
_value = value;
}
// const成员函数
int getValue() const {
return _value;
}
// 非const成员函数调用const成员函数
void printValue() {
std::cout << "Value: " << getValue() << std::endl; // 调用const成员函数
}
// const成员函数尝试调用非const成员函数
void trySetValue(int value) const {
// setValue(value); // 错误:const成员函数不能调用非const成员函数
}
private:
int _value;
};
int main() {
MyClass obj(42);
// 非const对象可以调用非const成员函数
obj.setValue(100);
// 非const对象可以调用const成员函数
std::cout << "Value: " << obj.getValue() << std::endl;
// const对象不能调用非const成员函数
const MyClass constObj(42);
// constObj.setValue(100); // 错误:const对象不能调用非const成员函数
// const对象可以调用const成员函数
std::cout << "Value: " << constObj.getValue() << std::endl;
return 0;
}
MyClass(int value) : _value(value) {}
构造函数初始化成员变量_value
。
const
成员函数void setValue(int value) {
_value = value;
}
非const
成员函数可以修改成员变量。
const
成员函数int getValue() const {
return _value;
}
const
成员函数不能修改成员变量。
const
成员函数调用const
成员函数void printValue() {
std::cout << "Value: " << getValue() << std::endl;
}
非const
成员函数可以调用const
成员函数。
const
成员函数尝试调用非const
成员函数void trySetValue(int value) const {
// setValue(value); // 错误:const成员函数不能调用非const成员函数
}
const
成员函数不能调用非const
成员函数。
main
函数示例int main() {
MyClass obj(42);
// 非const对象可以调用非const成员函数
obj.setValue(100);
// 非const对象可以调用const成员函数
std::cout << "Value: " << obj.getValue() << std::endl;
// const对象不能调用非const成员函数
const MyClass constObj(42);
// constObj.setValue(100); // 错误:const对象不能调用非const成员函数
// const对象可以调用const成员函数
std::cout << "Value: " << constObj.getValue() << std::endl;
return 0;
}
const
取地址操作符重载在C++中,取地址运算符(&
)和const
取地址运算符是两个默认成员函数,编译器会自动生成这些函数。通常情况下,我们不需要重新定义它们。但在某些特殊情况下,例如我们希望控制取地址运算符的行为,让它返回特定的内容时,才需要重载它们。下面我们将详细解释这些概念,并通过代码示例帮助理解。
&
)取地址运算符用于获取对象的内存地址。在大多数情况下,编译器会生成默认的取地址运算符。但有时候我们希望取地址运算符返回特定的内容,这时就需要重载它。
const
取地址运算符const
取地址运算符类似于取地址运算符,但它只能在const
对象上调用。编译器也会生成默认的const
取地址运算符,我们可以根据需要重载它。
为了帮助理解,我们将实现一个示例类 MyClass
,并重载其取地址运算符和const
取地址运算符。
#include <iostream>
class MyClass {
public:
MyClass(int value) : _value(value) {}
// 重载取地址运算符
int* operator&() {
std::cout << "调用重载的取地址运算符" << std::endl;
return &_value;
}
// 重载const取地址运算符
const int* operator&() const {
std::cout << "调用重载的const取地址运算符" << std::endl;
return &_value;
}
void display() const {
std::cout << "Value: " << _value << std::endl;
}
private:
int _value;
};
int main() {
MyClass obj(42);
const MyClass constObj(100);
obj.display();
constObj.display();
// 调用重载的取地址运算符
int* addr = &obj;
std::cout << "非const对象地址指向的值: " << *addr << std::endl;
// 调用重载的const取地址运算符
const int* constAddr = &constObj;
std::cout << "const对象地址指向的值: " << *constAddr << std::endl;
return 0;
}
构造函数:用于初始化对象的成员变量 _value
。
重载取地址运算符:返回对象的 _value
的地址,并打印一条信息。
int* operator&() {
std::cout << "调用重载的取地址运算符" << std::endl;
return &_value;
}
重载const
取地址运算符:返回const
对象的 _value
的地址,并打印一条信息。
const int* operator&() const {
std::cout << "调用重载的const取地址运算符" << std::endl;
return &_value;
}
显示成员函数:显示对象的 _value
。
void display() const {
std::cout << "Value: " << _value << std::endl;
}
main
函数:创建非const
对象和const
对象,调用重载的取地址运算符和const
取地址运算符。
int main() {
MyClass obj(42);
const MyClass constObj(100);
obj.display();
constObj.display();
// 调用重载的取地址运算符
int* addr = &obj;
std::cout << "非const对象地址指向的值: " << *addr << std::endl;
// 调用重载的const取地址运算符
const int* constAddr = &constObj;
std::cout << "const对象地址指向的值: " << *constAddr << std::endl;
return 0;
}
运行上面的代码,将会输出以下内容:
Value: 42
Value: 100
调用重载的取地址运算符
非const对象地址指向的值: 42
调用重载的const取地址运算符
const对象地址指向的值: 100