C/C++ - 类的封装特性

目录

类的封装

语法格式

声明定义

分文件

访问权限

类作用域

对象模型

构造函数

默认构造函数

带参构造函数

拷贝构造函数

构造函数重载

委托构造函数

初始数据列表

构造默认参数

构造函数删除

析构函数

析构函数概念

析构函数特性

析构函数示例

析构调用顺序

析构调用时机

浅拷贝深拷贝

静态成员

静态变量

静态函数

静态特性

静态特性

常量成员

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;
      }
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/353436.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QEMU源码全解析41 —— Machine(11)

接前一篇文章&#xff1a;QEMU源码全解析40 —— Machine&#xff08;10&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 时间过去了几…

Redis实现多种限流算法

一 常见限流算法 1 固定窗口限流 每一个时间段计数器&#xff0c;当计数器达到阈值后拒绝&#xff0c;每过完这个时间段&#xff0c;计数器重置0&#xff0c;重新计数。 优点&#xff1a;实现简单&#xff0c;性能高&#xff1b; 缺点&#xff1a;明显的临界问题&#xff0c…

2024.1.25 C++QT 作业

思维导图 练习题 1. 自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void sh…

【Go】Channel底层实现 ②

文章目录 channel底层实现channel发送、接收数据有缓冲 channelchannel 先写再读channel 先读再写(when the receiver comes first) 无缓冲channelchannel存在3种状态&#xff1a; channel底层实现 // channel 类型定义 type hchan struct {// channel 中的元素数量, lenqcoun…

图文解析交流慢充原理和握手协议以及OBC工作原理

1.接口定义 2.硬件连接原理 2.obc工作原理 OBC里面包括单片机1和单片机2&#xff0c;DSP。 有的厂家方案只有一个单片机&#xff0c;CC/CP部分直接用DSP实现。交流桩的ARM控制K1、K2&#xff0c;S1。单片机1控制K3。单片机2控制S2。DSP控制K4。BMS控制PDU里面的K5&#x…

算法分析(概论)

目录 第一章 概论 1.算法的概念 1.定义 2.算法设计要求 3.算法的特性 4.算法描述 5.数据结构与算法 6.算法设计的基本步骤 2.算法分析 1.计算机资源 2.算法分析 3.评判算法效率的方法 4.算法时间复杂度分析 5.渐进符号 1.大Ο符号 2.大Ω符号 3.大Θ符号 4.三…

Allure 内置特性

章节目录&#xff1a; 一、内置特性概述二、展示环境信息三、测试结果分类四、用例步骤说明五、添加附件六、添加用例描述七、设置动态的用例标题八、报告中添加链接九、组织测试结果9.1 使用与理解9.2 指定运行 十、划分用例级别十一、动态生成附加信息十二、清空历史报告记录…

Cesium反向遮罩指定区域挖空---Primitive、PolygonGeometry、PolylineGeometry实现

PolylineRegionalExcavationFun2() {import("./data/安徽省.json").then((res) => {console.log(`res`, res);let features = res.features;let positionArray = [];let borderLinePositionArray = [];// 获取区域的经纬度坐标if (features[0]?.geometry?.coord…

【大数据】Flink 中的状态管理

Flink 中的状态管理 1.算子状态2.键值分区状态3.状态后端4.有状态算子的扩缩容4.1 带有键值分区状态的算子4.2 带有算子列表状态的算子4.3 带有算子联合列表状态的算子4.4 带有算子广播状态的算子 在前面的博客中我们指出&#xff0c;大部分的流式应用都是有状态的。很多算子都…

【陈工笔记-Transformer】Transformer的基础认识

对Transformer生动形象的比喻 Transformer包括了Encoder和Decoder&#xff0c;在知乎上看到了对两个部分关系的一种理解&#xff0c;非常有趣。即&#xff0c;“一个人学习跳舞&#xff0c;Encoder是看别人是如何跳舞的&#xff0c;Decoder是将学习到的经验和记忆&#xff0c;…

被动信息搜集

被动信息搜集主要通过搜索引擎或者社交等方式对目标资产信息进行提取&#xff0c; 通常包括IP查询、Whois查询、子域名搜集等。进行被动信息搜集时不与目标产 生交互&#xff0c;可以在不接触到目标系统的情况下挖掘目标信息。主要方法包括&#xff1a;DNS 解析、子域名挖掘、…

Unity中创建Ultraleap 3Di交互项目

首先&#xff0c;创建新的场景 1、创建一个空物体&#xff0c;重命名为【XP Leap Provider Manager】&#xff0c;并在这个空物体上添加【XR Leap Provider Manager】 在物体XP Leap Provider Manager下&#xff0c;创建两个子物体Service Provider(XR)和Service Provider(…

随机点名--好玩哦

大屏滚动&#xff0c;随机点名&#xff0c;可刺激哦 想屏幕名字滚动得快一点&#xff0c;sleep时间就小一点 效果图 代码 #!/bin/bash namefile"/opt/name.txt" linenum$(sed -n $ $namefile) while : docleartmp$(sed -n "$[RANDOM%linenum1]p" $namefi…

文件上传之大文件分块上传

原则&#xff1a;合久必分&#xff0c;分久必合 优势部分&#xff1a;减少了内存占用&#xff0c;可实现断点续传&#xff0c;并发处理&#xff0c;利用带宽&#xff0c;提高效率 不足之处&#xff1a;增加复杂性&#xff0c;增加额外计算存储 应用场景&#xff1a;云存储大文件…

Springboot的 Lombok全部关联注解以及核心注解@Data详解

目录 工具安装 依赖注入 注解类别 1. Getter / Setter 2. ToString 3. EqualsAndHashCode 4. NoArgsConstructor / RequiredArgsConstructor / AllArgsConstructor 5. Data 示例 注意事项 6. Value 7. Builder 8. Slf4j / Log / Log4j / Log4j2 / XSlf4j 9. NonN…

03.领域驱动设计:了解实体和值对象以及它们的区别

目录 1、概述 2、实体 1.实体的业务形态 2.实体的代码形态 3.实体的运行形态 4.实体的数据库形态 3、值对象 1.值对象的业务形态 2.值对象的代码形态 3.值对象的运行形态 4.值对象的数据库形态 5.值对象的优势和局限 4、实体和值对象的区别 5、总结 1、概述 DDD战…

企业虚拟机服务器中了lockbit3.0勒索病毒怎么办,lockbit3.0勒索病毒解密处理流程

对于企业来说&#xff0c;企业的数据是企业的核心命脉&#xff0c;关乎着企业的生产与运营的所有工作。随着网络技术的不断发展&#xff0c;网络安全威胁也在不断增加。近期&#xff0c;云天数据恢复中心接到了很多企业的求助&#xff0c;企业的虚拟机服务器遭到了lockbit3.0勒…

vue的pinia环境搭建

一、 pinia是什么&#xff1f; Pinia是Vue的新一代轻量级状态管理库&#xff0c;它允许您跨组件/页面共享状态。Pinia由Vue.js官方成员重新设计&#xff0c;旨在提供更直观、更易于学习的状态管理解决方案。 Pinia的主要特点包括&#xff1a; 对Vue2和Vue3提供良好的支持&#…

机器学习之pandas库学习

这里写目录标题 pandas介绍pandas核心数据结构SeriesDataFrameDataFrame的创建列访问列添加列删除行访问行添加行删除数据修改 pandas介绍 pandas是基于NumPy 的一种工具&#xff0c;该工具是为了解决数据分析任务而创建的。Pandas 纳入 了大量库和一些标准的数据模型&#xff…

C#学习(十一)——Array和Collection

一、集合 集合重要且常用 孤立的数据是没有意义的&#xff0c;集合可以作为大量数据的处理&#xff0c;可进行数据的搜索、迭代、添加、删除。 C#中&#xff0c;所有集合都必须实现ICollection接口&#xff08;数组Array除外&#xff09; 集合说明Array数组&#xff0c;固定长…
最新文章