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

news2024/11/15 19:53:44

目录

类的封装

语法格式

声明定义

分文件

访问权限

类作用域

对象模型

构造函数

默认构造函数

带参构造函数

拷贝构造函数

构造函数重载

委托构造函数

初始数据列表

构造默认参数

构造函数删除

析构函数

析构函数概念

析构函数特性

析构函数示例

析构调用顺序

析构调用时机

浅拷贝深拷贝

静态成员

静态变量

静态函数

静态特性

静态特性

常量成员

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.coloradmin.cn/o/1415494.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity】【游戏开发】Pico打包后项目出现运行时错误如何Debug

【背景】 开发过程中的报错可以通过控制台查看&#xff0c;但是PICO项目这类依赖特定设备环境的应用往往存在打包后在设备端发生运行时错误。这时如何能查看到Debug信息呢&#xff1f; 【分析】 Pico也是安卓系统&#xff0c;所以这个问题就可以泛化为Unity有哪些在安卓端运…

dnSpy调试工具二次开发2-输出日志到控制台

本文在上一篇文章的基础上继续操作&#xff1a; dnSpy调试工具二次开发1-新增菜单-CSDN博客 经过阅读dnSpy的源码&#xff0c;发现dnSpy使用到的依赖注入用了MEF框架&#xff0c;所以在源码中可以看到接口服务类的上面都打上了Export的特性或在构造方法上面打上ImportingConst…

力扣hot100 最小栈 变种栈

Problem: 155. 最小栈 文章目录 思路&#x1f496; Stack 自定义 Node&#x1f37b; Code 思路 &#x1f469;‍&#x1f3eb; 甜姨 &#x1f496; Stack 自定义 Node 时间复杂度: O ( 1 ) O(1) O(1) 空间复杂度: O ( n ) O(n) O(n) &#x1f37b; Code class MinS…

数据结构-顺序表的实现 [王道]

本博客记录个人寒假学习内容。此篇博客内容为 顺序表的定义。 博客中截图来自王道数据结构公开课 目录 顺序表的定义 顺序表的特点 顺序表的实现--静态分配 顺序表的实现--动态分配 顺序表的定义--知识结构框架 顺序表的定义 >线性表是具有相同(每个数据元素所占的空间…

Spring Boot使用AOP

一、为什么需要面向切面编程&#xff1f; 面向对象编程&#xff08;OOP&#xff09;的好处是显而易见的&#xff0c;缺点也同样明显。当需要为多个不具有继承关系的对象添加一个公共的方法的时候&#xff0c;例如日志记录、性能监控等&#xff0c;如果采用面向对象编程的方法&…

CSS优先级内容

定义CSS样式时&#xff0c;经常出现两个或多个样式规则应用在同一元素的情况&#xff0c;这时就会出现优先级的情况&#xff0c;那么应用的元素应该显示哪一个样式呢&#xff1f; 一.下面举例对优先级进行具体讲解。 p{color:red;} .blue{color:orange;} #header{color:blu…

OpenCV-27 Canny边缘检测

一、概念 Canny边缘检测算法是John F.Canny与1986年开发出来的一个多级边缘检测算法&#xff0c;也被很多人认为是边缘检测的最优算法。最优边缘检测的三个主要评价标准是&#xff1a; 低错频率&#xff1a;表示出尽可能多的实际边缘&#xff0c;同时尽可能的减小噪声产生的误…

Spring源码分析:refresh()

refresh()中共有13个方法&#xff0c;分别为 1.prepareRefresh() 容器刷新前的准备&#xff0c;设置上下文状态&#xff0c;获取属性&#xff0c;验证必要的属性等 protected void prepareRefresh() {//spring启动时间this.startupDate System.currentTimeMillis();//spring…

01 Redis的特性+下载安装启动+Redis自动启动+客户端连接

1.1 NoSQL NoSQL&#xff08;“non-relational”&#xff0c; “Not Only SQL”&#xff09;&#xff0c;泛指非关系型的数据库。 键值存储数据库 &#xff1a; 就像 Map 一样的 key-value 对。如Redis文档数据库 &#xff1a; NoSQL 与关系型数据的结合&#xff0c;最像关系…

Python如何获取程序打包后的目录,如何获取管理员权限

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 实现步骤 📒📝 获取程序打包后所在目录📝 获取管理员权限⚓️ 相关链接 ⚓️📖 介绍 📖 Python 是一种功能强大的编程语言,本篇文章将介绍Python如何获取程序打包后所在目录,以及如何获取管理员权限并执行需要管理…

【深度学习】sdxl中的 tokenizer tokenizer_2 区别

代码仓库&#xff1a; https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/tree/main 截图&#xff1a; 为什么有两个分词器 tokenizer 和 tokenizer_2&#xff1f; 在仔细阅读这些代码后&#xff0c;我们了解到 tokenizer_2 主要是用于 refiner 模型的。 #…

javax.servlet.http包

javax.servlet.http包 javax.srvlet.http包是对javax.servlet包的扩展。该包的类和接口处理使用HTTP进行通信的servlet。这些servlet也称为HTTP Servlet。您需要扩展HttpServlet类来开发HTTP Servlet。javax.servlet.http包经常使用的接口包括: HttpServletRequest接口HttpSe…

Windows10上通过MSYS2编译FFmpeg 6.1.1源码操作步骤

1.从github上clone代码&#xff0c;并切换到n6.1.1版本&#xff1a;clone到D:\DownLoad目录下 git clone https://github.com/FFmpeg/FFmpeg.git git checkout n6.1.1 2.安装MSYS2并编译FFmpeg源码: (1).从https://www.msys2.org/ 下载msys2-x86_64-20240113.exe &#…

x-cmd pkg | shtris - 命令行俄罗斯方块游戏

目录 简介首次用户技术特点竞品和相关作品进一步阅读 简介 shtris 是一个由 shell 脚本&#xff0c;参考 俄罗斯方块指南 (2009) 实现的俄罗斯方块游戏。 首次用户 使用 x shtris 即可自动下载并使用 在终端运行 eval "$(curl https://get.x-cmd.com)" 即可完成 x …

[TCP协议]基于TCP协议的字典服务器

目录 1.TCP协议简介: 2.TCP协议在Java中封装的类以及方法 3.字典服务器 3.1服务器代码: 3.2客户端代码: 1.TCP协议简介: TCP协议是一种有连接,面向字节流,全双工,可靠的网络通信协议.它相对于UDP协议来说有以下几点好处: 1.它是可靠传输,相比于UDP协议,传输的数据更加可靠…

在ubuntu上在安装Squid代理服务器

Squid 是一个代理和缓存服务器&#xff0c;它将请求转发到所需的目的地&#xff0c;同时保存请求的内容&#xff0c;当你再次请求相同内容时&#xff0c;他可以向你提供缓冲内容&#xff0c;从而提高访问速度。Squid代理服务器目前支持的协议有&#xff1a;http、SSL、DNS、FTP…

【机组】单元模块的软件简介和安装

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《机组 | 模块单元实验》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 目录 【软件简介和安装】 1 性能特…

JVM篇:垃圾回收算法

标记清除 通过遍历GC Root后得到不再被引用的对象&#xff0c;对没被引用的对象做一个标记处理&#xff0c;然后对其进行清除。 优点&#xff1a;速度快 缺点&#xff1a;会产生内存碎片&#xff0c;可能会导致空闲的内存足够保存对象&#xff0c;但由于不连续而保存失败。 标…

Linux操作系统运维-用户与用户组管理

Linux操作系统运维-用户与用户组管理 用户种类与标识查看 超级用户&#xff08;root&#xff09;&#xff1a;可以不受限制地执行所有操作&#xff0c;拥有系统最高权限&#xff0c;修改系统设置与管理用户均需要root权限系统用户&#xff08;system&#xff09;&#xff1a;…

S275智慧煤矿4G物联网网关:矿山开采的未来已来

随着经济发展煤矿需求不断激增&#xff0c;矿山矿井普遍处于偏远山区&#xff0c;生产管理、人员安全、生产效率是每个矿山矿井都需要考虑的问题&#xff0c;利用网关对现场终端设备连接组网&#xff0c;实现智慧煤矿远程管理。 各矿山矿井分布范围比较广泛&#xff0c;户外环…