目录
类的封装
语法格式
声明定义
分文件
访问权限
类作用域
对象模型
构造函数
默认构造函数
带参构造函数
拷贝构造函数
构造函数重载
委托构造函数
初始数据列表
构造默认参数
构造函数删除
析构函数
析构函数概念
析构函数特性
析构函数示例
析构调用顺序
析构调用时机
浅拷贝深拷贝
静态成员
静态变量
静态函数
静态特性
静态特性
常量成员
const成员变量
const成员函数
const成员函数重载机制
const类的对象
对象指针
类的封装
-
语法格式
-
class classname { //默认属性 private: //成员变量 //成员函数 };
- class - 关键字定义类
- classname - 遵守标识符命名规则
-
-
声明定义
-
类主体
class Person { private: std::string m_Name; int m_Age; public: void SetAge(int nAge) { m_Age = nAge; } int GetAge() { return m_Age; } };
-
分文件
-
头文件
#pragma once #include <iostream> #include <string> class CPerson { //私有属性 private: //成员变量 std::string m_Name; int m_Age; //公共属性 public: //成员函数 void SetAge(int nAge); int GetAge(); };
-
源文件
#include "CPerson.h" void CPerson::SetAge(int nAge) { m_Age = nAge; } int CPerson::GetAge() { return m_Age; }
-
-
-
访问权限
-
public:公共成员在类内部和外部均可访问。它们对外部用户公开,可以自由访问。
-
private:私有成员仅在类内部可访问。它们对外部用户隐藏,只能通过类的公共成员函数进行访问。
-
protected:受保护成员在类内部可访问,也可以在派生类中访问。它们对外部用户隐藏,但可以被派生类继承并访问。
-
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
-
class的默认访问权限为private,struct为public。
-
示例代码
#include <iostream> class Person { //公共 public: int m_Age; void SetAge(int nAge) { m_Age = nAge; } int PublicGetMoney() { //类内部可以访问私有成员函数 return GetMoney(); } //保护 protected: //私有 private: int m_Money; int GetMoney() { return m_Money; } }; int main() { //实例化对象 Person p1; //公共权限 -> 类外部可以访问 p1.m_Age = 18; p1.SetAge(20); //私有属性 -> 类外部无法访问 //p1.m_Money; p1.PublicGetMoney(); return 0; }
-
-
类作用域
-
类作用域:类作用域是指在类的定义内部声明的成员,在整个类中可见。
-
对象作用域:对象作用域是指在类的对象中,通过对象名访问的成员。对象作用域仅限于该对象。
-
类名作用域:类名作用域是指在类的外部,使用类名和作用域解析运算符(::)访问的成员。
-
代码示例
#include <iostream> //全局变量 int a = 20; namespace CC_STD { int a = 30; } class Person { public: int m_Age; int GetAge(); static int Ver; private: void SetAge(int Age) { m_Age = Age; } }; int Person::GetAge() { return m_Age; } int Person::Ver = 0; int main() { int a = 10; //局部变量 std::cout << a << std::endl; //全局变量 std::cout << ::a << std::endl; //命名空间 std::cout << CC_STD::a << std::endl; //类实例化 Person p1; p1.m_Age = 18; //类名输出 std::cout << Person::Ver << std::endl; return 0; }
-
-
对象模型
-
C++类的对象内存结构布局描述了类对象在内存中的存储方式。了解对象内存结构布局有助于理解类对象的成员变量和成员函数在内存中的位置和访问方式。
-
成员变量的存储:类的成员变量按照声明的顺序存储在对象内存中。每个成员变量占据一定的内存空间,根据数据类型的大小而定。
#include <iostream> //空类 class c1 { }; //成员函数 = 1 class c2 { void Fun1() {}; }; //成员函数 = 2 class c3 { void Fun1() {}; void Fun2() {}; }; //成员变量 = int class c4 { int Num; }; //成员变量 = int //成员函数 = 1 class c5 { int Num; void Fun1() {}; }; int main() { std::cout << sizeof(c1) << std::endl; //1 std::cout << sizeof(c2) << std::endl; //1 std::cout << sizeof(c3) << std::endl; //1 std::cout << sizeof(c4) << std::endl; //4 std::cout << sizeof(c5) << std::endl; //4 return 0; }
-
对齐方式:为了提高内存访问的效率,编译器通常会对成员变量进行对齐。对齐规则可以通过编译选项进行配置,也可以使用特定的对齐指令来修改。
#include <iostream> class c1 { char m_c; //1 short m_s; //2 int m_i; //4 long long m_ll; //8 //15 }; int main() { std::cout << sizeof(c1) << std::endl; //16 return 0; }
-
访问权限:成员变量的访问权限(公共、私有等)不会影响对象内存结构布局,所有成员变量都按照声明顺序存储。
#include <iostream> class c1 { public: void InitData() { m_A = 1; m_B = 2; m_C = 3; m_D = 4; } public: int m_A; private: int m_B; public: int m_C; private: int m_D; }; int main() { c1 c; c.InitData(); return 0; }
-
虚函数表指针(vptr):虚函数表指针是一个指向虚函数表的指针,它存在于包含虚函数的类对象中。虚函数表是一个存储着虚函数地址的表格,使得派生类的虚函数能够覆盖基类的虚函数。虚函数表指针通常位于对象内存布局的开头或结尾,用于在运行时动态查找并调用正确的虚函数。
-
-
-
构造函数
-
默认构造函数
- 当类定义中没有显式定义构造函数时,编译器会自动生成一个默认构造函数。
- 默认构造函数是一个没有任何参数的构造函数。
- 默认构造函数用于创建对象时进行初始化操作。
//默认构造函数 classname() { }
#include <iostream> class MyClass { public: // 默认构造函数 MyClass() { std::cout << "默认构造函数被调用" << std::endl; } }; int main() { // 创建对象时调用默认构造函数 MyClass obj; return 0; }
-
带参构造函数
- 带参数的构造函数可以接受参数,并用这些参数对对象进行初始化。
- 通过在类定义中声明带参数的构造函数,可以自定义对象的初始化过程。
#include <iostream> class MyClass { public: int value; // 默认构造函数 MyClass() { std::cout << "默认构造函数被调用,value = " << value << std::endl; } // 带参构造函数 MyClass(int num) { value = num; std::cout << "带参构造函数被调用,value = " << value << std::endl; } }; int main() { // 创建对象时调用默认构造函数 MyClass obj1; // 创建对象时调用带参构造函数 MyClass obj2(10); return 0; }
-
拷贝构造函数
- 如果没有显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。
- 拷贝构造函数用于创建一个新对象,并将其初始化为现有对象的副本。
- 拷贝构造函数的参数是同类型的对象的引用。
#include <iostream> class MyClass { public: int value; // 默认构造函数 MyClass() { std::cout << "默认构造函数被调用" << std::endl; } // 带参构造函数 MyClass(int num) { value = num; std::cout << "带参构造函数被调用,value = " << value << std::endl; } // 复制构造函数 MyClass(const MyClass& other) { value = other.value; std::cout << "复制构造函数被调用,value = " << value << std::endl; } }; int main() { // 创建对象时调用带参构造函数 MyClass obj1(10); // 使用复制构造函数创建新对象 MyClass obj2 = obj1; return 0; }
-
构造函数重载
- 类可以具有多个构造函数,这称为构造函数的重载。
- 构造函数的重载允许使用不同的参数列表来创建对象。
- 编译器根据提供的参数来确定应该调用哪个构造函数。
#include <iostream> class MyClass { public: int value1; int value2; // 构造函数重载 MyClass() { value1 = 0; value2 = 0; std::cout << "默认构造函数被调用" << std::endl; } MyClass(int num) { value1 = num; value2 = 0; std::cout << "带一个参数的构造函数被调用,value1 = " << value1 << std::endl; } MyClass(int num1, int num2) { value1 = num1; value2 = num2; std::cout << "带两个参数的构造函数被调用,value1 = " << value1 << ", value2 = " << value2 << std::endl; } }; int main() { // 调用不同的构造函数 MyClass obj1; MyClass obj2(10); MyClass obj3(20, 30); return 0; }
-
委托构造函数
- 委托构造函数允许一个构造函数调用同一个类的其他构造函数来完成对象的初始化。
- 委托构造函数使用冒号(:)语法来调用其他构造函数。
#include <iostream> class MyClass { public: int value1; int value2; // 委托构造函数 MyClass() : MyClass(0, 0) { std::cout << "默认构造函数被调用" << std::endl; } MyClass(int num) : MyClass(num, 0) { std::cout << "带一个参数的构造函数被调用,value1 = " << value1 << std::endl; } MyClass(int num1, int num2) { value1 = num1; value2 = num2; std::cout << "带两个参数的构造函数被调用,value1 = " << value1 << ", value2 = " << value2 << std::endl; } }; int main() { // 调用不同的构造函数 MyClass obj1; MyClass obj2(10); MyClass obj3(20, 30); return 0; }
-
初始数据列表
- 初始化列表用于在构造函数中初始化类的成员变量。
- 初始化列表使用冒号(:)后跟成员变量的初始化。
- 初始化列表可以提供更高效的初始化方式,尤其是对于成员变量是常量或引用类型的情况。
#include <iostream> class MyClass { public: int value; // 构造函数使用初始化列表 MyClass() : value(10) { std::cout << "构造函数被调用,value = " << value << std::endl; } }; int main() { // 创建对象时调用带参数的构造函数 MyClass obj; return 0; }
-
构造默认参数
- 构造函数可以有默认参数值,这样在创建对象时可以省略对应参数的传递。
- 默认参数值在构造函数声明中指定,而不是在定义中。
#include <iostream> class MyClass { public: int value1; int value2; // 带默认参数的构造函数 MyClass(int num1 = 0, int num2 = 0) : value1(num1), value2(num2) { std::cout << "构造函数被调用,value1 = " << value1 << ", value2 = " << value2 << std::endl; } }; int main() { // 调用构造函数时省略参数 MyClass obj1; // value1 = 0, value2 = 0 MyClass obj2(10); // value1 = 10, value2 = 0 MyClass obj3(20, 30); // value1 = 20, value2 = 30 return 0; }
-
构造函数删除
- 可以使用关键字 delete 在类中显式删除构造函数。
-
删除构造函数将阻止该构造函数的调用,从而禁止使用特定的构造方式。
#include <iostream> class MyClass { public: // 删除默认构造函数 MyClass() = delete; // 删除拷贝构造函数 MyClass(const MyClass&) = delete; // 构造函数 MyClass(int num) { std::cout << "构造函数被调用,num = " << num << std::endl; } }; int main() { // 无法调用已删除的构造函数 //MyClass obj1; // 错误,无法调用已删除的默认构造函数 //MyClass obj2(obj1); // 错误,无法调用已删除的拷贝构造函数 MyClass obj4(10); // 正确,调用构造函数 return 0; }
-
-
析构函数
-
析构函数概念
- 析构函数是在对象销毁时自动调用的特殊成员函数。
- 析构函数用于清理对象分配的资源、释放内存和执行其他必要的清理操作。
-
析构函数特性
- 析构函数的名称与类名相同,前面加上波浪号(~)作为前缀。
- 析构函数没有返回类型,包括void。
- 析构函数没有参数。
- 析构函数不能被重载。
-
析构函数示例
-
析构函数通常用于释放在对象生命周期期间分配的资源,如堆上的内存、打开的文件、网络连接等。
#include <iostream> class MyClass { public: // 构造函数 MyClass() { std::cout << "构造函数被调用" << std::endl; } // 析构函数 ~MyClass() { std::cout << "析构函数被调用" << std::endl; } }; int main() { { MyClass obj; // 创建一个对象 } // 对象超出作用域,析构函数被调用 return 0; }
-
-
析构调用顺序
- 如果类继承了其他类,那么析构函数将按照构造函数的调用顺序相反的顺序被调用。
- 先调用派生类的析构函数,然后调用基类的析构函数。
#include <iostream> class Base { public: Base() { std::cout << "Base的构造函数被调用" << std::endl; } ~Base() { std::cout << "Base的析构函数被调用" << std::endl; } }; class Derived : public Base { public: Derived() { std::cout << "Derived的构造函数被调用" << std::endl; } ~Derived() { std::cout << "Derived的析构函数被调用" << std::endl; } }; int main() { Derived obj; // 创建一个派生类对象 return 0; }
-
析构调用时机
- 对象的析构函数在以下情况下被自动调用:
- 对象超出作用域。
- 对象作为局部变量在函数执行完毕后销毁。
- 对象动态分配内存后使用delete释放。
- 对象作为成员变量,其所属的对象被销毁时。
#include <iostream> class Person { public: Person(const char* szName, int nAge) { //年龄赋值 m_Age = nAge; m_Name = NULL; //内存申请 char* szBuffer = (char*)malloc(strlen(szName) + 1/*'/0'*/); if (szBuffer) { memset(szBuffer, 0, strlen(szName) + 1); memcpy(szBuffer, szName, strlen(szName)); m_Name = szBuffer; } } ~Person() { if (m_Name) { free(m_Name); m_Name = NULL; } } char* m_Name; int m_Age; }; int main() { Person p1("0xCC", 18); return 0; }
-
浅拷贝深拷贝
- 浅拷贝
- 深拷贝
#include <iostream> class Person { public: Person(const char* szName, int nAge) { //年龄赋值 m_Age = nAge; m_Name = NULL; //内存申请 char* szBuffer = (char*)malloc(strlen(szName) + 1/*'/0'*/); if (szBuffer) { memset(szBuffer, 0, strlen(szName) + 1); memcpy(szBuffer, szName, strlen(szName)); m_Name = szBuffer; } } Person(const Person& ref) { m_Name = NULL; m_Age = ref.m_Age; if (ref.m_Name) { //内存申请 char* szBuffer = (char*)malloc(strlen(ref.m_Name) + 1/*'/0'*/); if (szBuffer) { memset(szBuffer, 0, strlen(ref.m_Name) + 1); memcpy(szBuffer, ref.m_Name, strlen(ref.m_Name)); m_Name = szBuffer; } } } ~Person() { if (m_Name) { free(m_Name); m_Name = NULL; } } char* m_Name; int m_Age; }; int main() { Person p1("0xCC", 18); Person p2(p1); return 0; }
-
-
静态成员
-
静态变量
- 静态变量是在类中声明的静态成员变量,它与类的任何对象都无关,只有一个副本。
- 静态变量在类的所有对象之间共享,它们存储在静态存储区,直到程序结束才会被销毁。
- 静态变量可以公开或私有,可以通过类名或对象访问,也可以在类外部初始化。
- 静态变量的访问权限与其他成员变量相同,可以是公有、私有或保护。
- 静态变量的声明通常放在类的声明中,但必须在类外部初始化。
#include <iostream> class MyClass { public: static int count; // 静态变量声明 MyClass() { count++; // 每次创建对象时,增加count的值 } ~MyClass() { count--; // 每次销毁对象时,减少count的值 } }; int MyClass::count = 0; // 静态变量初始化 int main() { MyClass obj1; std::cout << "Count: " << MyClass::count << std::endl; MyClass obj2; std::cout << "Count: " << obj2.count << std::endl; std::cout << "Count: " << obj1.count << std::endl; return 0; }
-
静态函数
- 静态函数是与类相关联的函数,它们属于整个类而不是类的实例。
- 静态函数在类的对象上调用,而不是特定对象上调用,因此它们不能访问非静态成员变量和非静态成员函数。
- 静态函数可以通过类名或对象调用,但通常习惯通过类名调用,例如ClassName::staticFunction()。
- 静态函数的声明和定义都在类的声明内部,并用static关键字标记。
#include <iostream> class MathUtility { public: static int add(int a, int b) { return a + b; } static int multiply(int a, int b) { return a * b; } }; int main() { int sum = MathUtility::add(3, 5); std::cout << "Sum: " << sum << std::endl; int product = MathUtility::multiply(4, 6); std::cout << "Product: " << product << std::endl; return 0; }
-
静态特性
-
静态成员函数无法访问非静态成员
- 静态成员函数只能访问静态成员变量和静态成员函数,无法直接访问非静态成员变量和非静态成员函数。
- 这是因为非静态成员是与类的实例相关联的,而静态成员函数是与类相关联的。
-
静态成员的作用域
- 静态成员的作用域范围限于类的定义域内,可以在类的任何成员函数中访问。
- 静态成员变量和静态成员函数可以在类的外部通过类名进行访问。
#include <iostream> class MyClass { public: static int count; // 静态成员变量声明 int value; // 非静态成员变量 static void incrementCount() { count++; // 静态成员函数可以访问静态成员变量 // value++; // 静态成员函数无法访问非静态成员变量 } void print() { std::cout << "Value: " << value << std::endl; } static void printCount() { std::cout << "Count: " << count << std::endl; // print(); // 静态成员函数无法直接调用非静态成员函数 } }; int MyClass::count = 0; // 静态成员变量初始化 int main() { MyClass obj1; obj1.value = 5; MyClass::incrementCount(); // 通过类名调用静态成员函数 obj1.print(); // 通过对象调用非静态成员函数 MyClass::printCount(); // 通过类名调用静态成员函数 return 0; }
-
-
静态特性
-
静态成员变量可以作为类的全局变量使用:
- 静态成员变量在类的作用域内可见,但它们的生命周期超出了类的对象。
- 这意味着可以在类的外部访问和修改静态成员变量,就像访问全局变量一样。
- 静态成员变量可以通过类名或对象进行访问,但推荐使用类名访问,以明确表达静态性质。
-
静态成员可以用于共享信息:
- 静态成员变量可以用于在类的所有对象之间共享数据。
- 这在跟踪类的实例数、记录与类相关的全局状态等方面非常有用。
-
静态成员可以用于实现工具函数:
- 静态成员函数可以作为类的工具函数,与特定对象无关地执行某些操作。
- 这些静态成员函数可以在没有创建类对象的情况下直接调用,提供了一种方便的方式来执行与类相关的操作。
-
友元函数可以访问静态成员:
- 如果将函数声明为类的友元函数,那么这个函数可以访问类的私有静态成员。
- 这可以用来提供对类的私有静态成员的特殊访问权限。
#include <iostream> class Circle { private: static const double PI; // 静态成员变量声明 public: static double calculateArea(double radius) { return PI * radius * radius; // 静态成员函数使用静态成员变量 } static void printPI() { std::cout << "PI: " << PI << std::endl; } friend void setPI(double value); // 声明友元函数 }; const double Circle::PI = 3.14159; // 静态成员变量初始化 void setPI(double value) { Circle::PI = value; // 友元函数可以访问类的私有静态成员 } int main() { double radius = 2.5; double area = Circle::calculateArea(radius); // 通过类名调用静态成员函数 std::cout << "Area: " << area << std::endl; Circle::printPI(); // 通过类名调用静态成员函数 setPI(3.14); // 调用友元函数设置静态成员变量的值 Circle::printPI(); return 0; }
-
-
-
常量成员
-
const成员变量
- 在类的声明中,通过在成员变量前添加const关键字来声明常量成员。
- 常量成员必须在构造函数的初始化列表中进行初始化(或者直接赋值)。
- 常量成员一旦初始化完成,其值将不能再修改。
#include <iostream> class MyClass { public: //成员变量 int m_a; //静态变量 static int m_b; //常量成员 const int m_Ver; //构造函数 MyClass() : m_Ver(1), m_a(0) { } }; int MyClass::m_b = 3; int main() { MyClass m1; std::cout << sizeof(m1) << std::endl; //4 return 0; }
-
const成员函数
-
在类中,可以将成员函数声明为const成员函数。const成员函数表示该函数不会修改类的成员变量。
class MyClass { public: void func() const { // 这是一个const成员函数 // 不能修改类的成员变量 } };
-
在类中,可以使用mutable关键字修饰成员变量,使其可以在const成员函数中修改。
class MyClass { public: void func() const { mutableVar = 10; // 在const成员函数中修改mutable成员变量 } private: mutable int mutableVar; // mutable成员变量 };
-
const成员函数重载机制
- 一个类可以有多个同名的成员函数,其中一个是const成员函数,另一个是非const成员函数。它们被视为重载函数,根据调用对象的const属性来选择调用哪个函数。
class MyClass { public: void func() { // 非const成员函数 } void func() const { // const成员函数 } };
-
const成员函数的使用场景
- 当你想确保对象在调用成员函数时不会被修改。
- 当你想通过一个const对象来访问该对象的成员函数。
- 当你想在const对象上调用成员函数时,以便在多线程环境中确保对象的线程安全性。
#include <iostream> class Person { public: Person(const std::string& szName):m_Name(szName){} const std::string& GetName() { std::cout << "const std::string& GetName()" << std::endl; return m_Name; } const std::string& GetName() const { std::cout << "const std::string& GetName() const" << std::endl; return m_Name; } private: const std::string m_Name; }; int main() { Person p1("0xCC"); std::string szName = p1.GetName(); std::cout << szName << std::endl; const Person p2("0xAA"); szName = p2.GetName(); std::cout << szName << std::endl; return 0; }
-
-
const类的对象
- 当一个对象被声明为const对象时,只能调用其const成员函数,而不能调用非const成员函数。
const MyClass obj; obj.func(); // 合法,调用const成员函数 // obj.nonConstFunc(); // 错误,不能调用非const成员函数 ```
- 在一个const类中,所有的成员函数都被自动视为const成员函数。这意味着在const类中,所有的成员函数都不能修改类的成员变量。
class ConstClass { public: void func() const { // const成员函数 } void modify() { // 错误,不能修改const类的成员变量 } }; int main() { const ConstClass obj; // const类对象 obj.func(); // 合法,调用const成员函数 // obj.modify(); // 错误,不能调用非const成员函数 return 0; }
-
-
对象指针
-
this指针作用
- this指针的作用域局限于非静态成员函数内部,它在函数体内是可见的。
- 在成员函数中,可以使用this指针来访问当前对象的成员变量和成员函数。
-
this指针用途
- 用于区分成员变量和参数变量:
当成员变量和成员函数的参数变量名相同时,可以使用this指针来明确指示访问的是成员变量。 - 在成员函数中返回当前对象:
this指针可以在成员函数中返回当前对象的指针,方便链式调用或其他操作。
- 用于区分成员变量和参数变量:
-
this指针特性
- this指针是一个隐含在每个非静态成员函数中的特殊指针,指向当前对象的地址。
- this指针可以在类的成员函数中使用,用于访问当前对象的成员变量和成员函数。
- 当调用一个成员函数时,编译器会自动将当前对象的地址作为this指针传递给函数。
- this指针是一个常量指针,不允许修改指向的对象。
#include <iostream> class MyClass { private: int data; public: // 构造函数 MyClass(int data) { this->data = data; // 使用this指针访问成员变量 } // 成员函数 void printData() { std::cout << "Data: " << this->data << std::endl; // 使用this指针访问成员变量 } void setData(int data) { this->data = data; // 使用this指针访问成员变量 } // 返回this指针的成员函数 MyClass* getObject() { return this; // 返回当前对象的地址 } }; int main() { MyClass obj1(10); // 创建一个对象 obj1.printData(); // 输出对象的成员变量 obj1.setData(20); // 修改对象的成员变量 obj1.printData(); // 输出修改后的成员变量 MyClass* ptr = obj1.getObject(); // 返回当前对象的地址 ptr->printData(); // 输出对象的成员变量 return 0; }
-
const成员函数和this指针
- const成员函数表示该函数不会修改对象的状态。
- 在const成员函数中,this指针的类型是const ClassName*,即指向常量对象的指针。
- 在const成员函数中,只能调用其他const成员函数,不能调用非const成员函数。
#include <iostream> class MyClass { private: int data; public: MyClass(int data) { this->data = data; } void printData() const { std::cout << "Data: " << this->data << std::endl; } void setData(int data) { this->data = data; } MyClass* getObject() { return this; } const MyClass* getConstObject() const { return this; } }; int main() { MyClass obj1(10); obj1.printData(); obj1.setData(20); obj1.printData(); MyClass* ptr = obj1.getObject(); ptr->printData(); const MyClass obj2(30); const MyClass* constPtr = obj2.getConstObject(); constPtr->printData(); return 0; }
-