👻类
👾语法格式
class className{
Access specifiers: // 访问权限
DataType variable; // 变量
returnType functions() { } // 方法
};
👾访问权限
class className {
public:
// 公有成员
protected:
// 受保护成员
private:
// 私有成员
};
👽公有成员 public
公有成员在类外部可访问,可不使用任何成员函数来设置和获取公有变量的值
class Line {
public:
double length;
void setLength(double len);
double getLength(void);
};
double Line::getLength(void) { return length ; }
void Line::setLength(double len) { length = len; }
...
Line line;
// 使用成员变量(正确,因为 length 公有)
line.length = 10.0;
cout << line.length <<endl;
// 使用成员函数
line.setLength(6.0);
cout << line.getLength() <<endl;
👽私有成员 private
私有成员在类外部不可访问,不可查看,只有类和友元函数可以访问私有成员。没有使用任何访问修饰符默认私有。
class Box {
public:
double length;
void setWidth(double wid);
double getWidth(void);
private:
double width;
};
double Box::getWidth(void) { return width; }
void Box::setWidth( double wid ) { width = wid; }
...
Box box;
// box.width = 10.0; // [Error] 'double Box::width' is private
box.setWidth(10.0); // 使用成员函数设置宽度
cout << box.getWidth() <<endl;
👽受保护成员 protected
受保护成员与私有成员相似,但有一点不同,受保护成员在**派生类(即子类)**中是可访问的。
/* 基类 */
class Box {
protected:
double width;
};
/* 派生类 */
class SmallBox: Box {
public:
void setSmallWidth(double wid);
double getSmallWidth();
};
double SmallBox::getSmallWidth() { return width; }
void SmallBox::setSmallWidth(double wid) { width = wid; }
...
SmallBox box;
box.setSmallWidth(5.0);
cout << box.getSmallWidth() << endl;
👻类成员函数
👾语法格式
-
定义在类定义内部
class className{ returnType functions(parameter list) { ... } };
-
单独使用范围解析运算符
::
定义class className{ returnType functions(parameter list); }; returnType class_name::functions(parameter list){ ... }
👾示例代码
-
定义在类定义内部
class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume() { return length * breadth * height; } };
-
单独使用范围解析运算符
::
定义class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(); }; double Box::getVolume() { return length * breadth * height; }
👻类构造函数
👾无参构造函数
👽语法格式
class className{
className() { ... }
};
👽示例代码
class Line {
public:
Line() { cout << "Object is being created" << endl; }
private:
double length;
};
...
Line line; // 输出:Object is being created
👾带参构造函数
👽语法格式
class className{
className(parameter list) { ... }
};
👽示例代码
class Line {
public:
Line(double len) {
length = len;
cout << "Object is being created" << endl;
}
private:
double length;
};
...
Line line; // 输出:Object is being created
👾初始化列表构造函数
👽语法格式
类有多个字段 A、B、C 等需要进行初始化:
class className{
className(int a,int b,int c): A(a), B(b), C(c) { ... }
};
👽示例代码
class Line {
public:
Line(double len): length(len) { cout << "Object is being created" << endl; }
private:
double length;
};
...
Line line; // 输出:Object is being created
等同语法:
Line(double len) {
length = len;
cout << "Object is being created" << endl;
}
注意:初始化类成员时,是按声明的顺序初始化,而不是按初始化列表的顺序初始化,即:
class Base { public: // 按照 A -> B -> C 的顺序初始化 int A; int B; int C; // 而不是按照 C -> B -> A 的顺序初始化 Base(int a, int b, int c): C(c), B(b), A(a) {} };
👻类析构函数
- 类析构函数在每次删除所创建的对象时执行。
- 析构函数的名称与类的名称是完全相同的,在前面加
波浪号(~)
作为前缀,不返回任何值,不能带有任何参数。 - 析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
👾语法格式
类有多个字段 A、B、C 等需要进行初始化:
class className{
~className() { ... }
};
👾示例代码
class Line {
public:
Line() { cout << "Object created" << endl; } // 构造函数声明
~Line() { cout << "Object deleted" << endl; } // 析构函数声明
};
...
Line line;
一个类内可以有多个构造函数(相当于重载构造函数),只有一个析构函数
class Matrix { public: Matrix(int row, int col); //普通构造函数 Matrix(const Matrix& matrix); //拷贝构造函数 Matrix(); //构造空矩阵的构造函数 ~Matrix(); };
👻类拷贝构造函数
👾语法结构
class className{
// obj是对象引用,用于初始化另一个对象
className (const className &obj) { }
};
👾被调用情况
-
情况1:用一个对象初始化同类的另一个对象时,调用被初始化的对象的拷贝构造函数
Base a2(a1); Base a2 = a1;
class Base { public: int num; // 一般构造函数 Base(int n) { num = n; cout << "General Constructor called" << endl; } // 拷贝构造函数 Base(const Base& a) { num = a.num; cout << "Copy Constructor called" << endl; } }; ... // 只调用a1的一般构造函数,输出 General Constructor called Base a1(1); // 只调用a2的拷贝构造函数,输出 Copy Constructor called Base a2(a1); Base a2 = a1; // 不调用 a2 = a1;
注意,
Base a2 = a1;
是初始化语句,不是赋值语句。赋值语句的等号左边是一个早已有定义的变量,不会引发复制构造函数的调用,
Base a1(1); // 调用a1的一般构造函数 Base a2(a1); // 调用a2的拷贝构造函数 a2 = a1; // 不调用,因为 a2 早已生成且初始化,不会引发拷贝构造函数
只会调用被初始化的对象的拷贝构造函数,不会调用其一般构造函数
-
情况2:函数参数是类的对象,当函数调用时,调用对象的拷贝构造函数
class Base { public: // 一般构造函数 Base() { cout << "General Constructor called" << endl; }; // 拷贝构造函数 Base(Base& a) { cout << "Copy constructor called" << endl; } }; void Function(Base a) {} ... Base a; // 调用a的一般构造函数,输出 General Constructor called Function(a); // 调用a的拷贝构造函数,输出 Copy constructor called
- 以对象作为形参,函数被调用时,生成形参要用拷贝构造函数,会带来时间开销
- 用对象的引用作为形参,无时间开销,但有一定的风险,因为如果形参的值发生改变,实参值也会改变
如果要确保实参值不改变,又希望避免拷贝构造函数的开销,解决办法是将形参声明为对象的
const
引用:void Function(const Base &a){ ... }
-
情况3:函数返回值是类的对象,当函数返回时,调用对象的拷贝构造函数
class Base { public: int num; // 一般构造函数 Base(int n) { num = n; cout << "General Constructor called" << endl; } // 拷贝构造函数 Base(const Base& a) { num = a.num; cout << "Copy constructor called" << endl; } }; Base Func() { Base a(4); // 调用一般构造函数,输出 General Constructor called return a; } ... int a = Func().num; // 调用拷贝构造函数,输出 Copy constructor called
有些编译器出于程序执行效率的考虑,编译时进行优化,函数返回值对象不用拷贝构造函数,不符合C++标准
👾深拷贝和浅拷贝
👽浅拷贝
- 使用情况:基本类型数据、简单对象
- 原理:按位复制内存
// 基本类型数据
int a = 10;
int b = a;
// 简单对象
class Base {
public:
Base(): m_a(0), m_b(0) { }
Base(int a, int b): m_a(a), m_b(b) { }
private:
int m_a;
int m_b;
};
Base obj1(10, 20);
Base obj2 = obj1;
👽深拷贝
- 使用情况:
- 持有其它资源的类(如动态分配的内存、指向其他数据的指针)
- 标准模板库(STL)中 string、vector、stack 等
- 创建对象时进行一些预处理工作,比如统计创建过的对象的数目、记录对象创建的时间等
- 原理:显式定义拷贝构造函数
- 过程:
- 会将原有对象的所有成员变量拷贝给新对象
- 会为新对象再分配一块内存,并将原有对象所持有的内存也拷贝过来
- 结果:原有对象和新对象所持有的动态内存相互独立,更改一个对象的数据不影响另一个对象
// 持有其它资源的类(如动态分配的内存、指向其他数据的指针)
#include <cstdlib>
#include <iostream>
using namespace std;
class Array {
public:
Array(const int len) {
m_len = len;
m_p = (int*)calloc(m_len, sizeof(int));
}
Array(const Array& arr) {
this->m_len = arr.m_len;
this->m_p = (int*)calloc(this->m_len, sizeof(int));
memcpy(this->m_p, arr.m_p, m_len * sizeof(int));
}
~Array() { free(m_p); }
int operator[](int i) const { return m_p[i]; }
int& operator[](int i) { return m_p[i]; }
int length() const { return m_len; }
private:
int m_len;
int* m_p;
};
void printArray(const Array& arr) {
int len = arr.length();
for(int i = 0; i < len; i++) {
if(i == len - 1) { cout << arr[i] << endl; }
else { cout << arr[i] << ", "; }
}
}
...
int main() {
Array arr1(10);
for(int i = 0; i < 10; i++)
arr1[i] = i;
Array arr2 = arr1;
arr2[5] = 100;
arr2[3] = 29;
printArray(arr1); // 输出:0, 1, 2, 3, 4, 5, 6, 7, 8, 9
printArray(arr2); // 输出:0, 1, 2, 29, 4, 100, 6, 7, 8, 9
}
// 创建对象时进行一些预处理工作
#include <iostream>
#include <ctime>
#include <windows.h>
using namespace std;
class Base {
public:
Base(int a = 0, int b = 0) {
m_a = a;
m_b = b;
m_count++;
m_time = time(nullptr);
}
Base(const Base& obj) {
this->m_a = obj.m_a;
this->m_b = obj.m_b;
this->m_count++;
this->m_time = time(nullptr);
}
static int getCount() { return m_count; }
time_t getTime() const { return m_time; }
private:
int m_a;
int m_b;
time_t m_time; //对象创建时间
static int m_count; //创建过的对象的数目
};
int Base::m_count = 0;
...
int main() {
Base obj1(10, 20);
cout << "obj1: count = " << obj1.getCount() << ", time = " << obj1.getTime() << endl;
// 输出:obj1: count = 1, time = 1740055359
Sleep(3000);
Base obj2 = obj1;
cout << "obj2: count = " << obj2.getCount() << ", time = " << obj2.getTime() << endl;
// 输出:obj2: count = 2, time = 1740055362
return 0;
}
👻类静态成员
👾类静态成员变量
👽声明
使用 关键字 static
把类成员声明为静态
class Box {
public:
static int objectCount; // 声明静态变量
Box() { objectCount++; } // 每次创建对象时调用构造函数,静态变量值加 1
};
👽定义
在类外部通过 范围解析运算符 ::
定义静态变量
class Box {
public:
static int objectCount; // 声明静态变量
Box() { objectCount++; }
};
// 仅定义却不初始化 类静态成员
int Box::objectCount;
无论是否初始化,必须定义,否则报错:
(.rdata$.refptr._ZN3Box11objectCountE[.refptr._ZN3Box11objectCountE]+0x0): undefined reference to `Box::objectCount'
👽初始化
// 定义+初始化 类静态成员
int Box::objectCount = 0;
若不初始化,则在创建第一个对象时所有静态数据初始化为零。
👽访问
- 使用 范围解析运算符
::
访问类静态变量
Box box1(); // 声明 box1
Box box2(); // 声明 box2
cout << "创建对象总数: " << Box::objectCount << endl;
👾类静态成员函数
👽声明定义
- 使用 关键字
static
把类成员声明为静态
class Box {
public:
static int objectCount; // 声明静态变量
Box() { objectCount++; }
static int getCount() { // 声明定义静态函数
return objectCount;
}
};
👽使用
- 使用 范围解析运算符
::
访问类静态变量
cout << "创建对象总数: " << Box::getCount() << endl; // 对象不存在时 也能被调用
Box box1(); // 声明 box1
Box box2(); // 声明 box2
cout << "创建对象总数: " << Box::getCount() << endl;
👻友元
👾友元函数
👽声明
使用 关键字 friend
把函数声明为友元函数
class Box {
double width;
public:
Box(double w): width(w) {}
friend void printWidth(Box box); // 在函数前使用关键字 friend
};
👽定义
class Box {
double width;
public:
Box(double w): width(w) {}
friend void printWidth(Box box);
};
/* printWidth() 不是任何类的成员函数,但因为它是 Box 的友元,可直接访问该类的任何成员 */
void printWidth(Box box) { cout << "Width of box : " << box.width <<endl;}
👽使用
Box box;
box.setWidth(10.0);
printWidth(box); // 使用友元函数访问Box中的私有成员
👾友元类
👽声明定义
class Box {
public:
Box(double w): width(w) {}
friend class BigBox;
private:
double width;
};
/* BigBox是Box的友元类,它可以直接访问Box类的任何成员 */
class BigBox {
public :
void printWidth(Box& box) { cout << "Width of box : " << box.width << endl; }
};
👽使用
Box box(10.0);
BigBox big;
big.printWidth(box); // 使用友元类中的方法访问Box中的私有成员
👻 this 指针
this
指针是一个特殊的指针,它指向当前对象的实例,每一个对象都能通过this
指针来访问自己的地址。- 当一个对象的成员函数被调用时,编译器会隐式地传递该对象的地址作为
this
指针。 - 友元函数没有
this
指针,因为友元不是类的成员
class MyClass {
private:
int value;
public:
// 可以明确地告诉编译器想访问当前对象的成员变量,而不是函数参数或局部变量,避免命名冲突
void setValue(int value) { this->value = value; }
void getValue() { return this->value; }
};
...
MyClass obj;
obj.setValue(42);
int value = obj.getValue();