第十二章:类(一)

本文详细介绍了C++中的结构体,包括声明与定义、数据成员的声明与初始化、静态数据成员的处理、mutable关键字的应用以及成员函数的相关知识。强调了结构体与类的区别,以及访问权限和友元的概念,帮助读者深入理解C++中结构体和对象的聚合特性。
摘要由CSDN通过智能技术生成

结构体与对象聚合

结构体是一种对基本数据结构进行扩展,并将多个对象放置在一起来视为一个整体的类型,比如

struct Str{
    int x;
    int y;
};

关于结构体,我们需要注意以下几点

  • 结构体的声明与定义(注意定义后面要跟分号来表示结束)
    struct Str;			// 声明
    
    struct Str{			// 定义
        int x;
        int y;
    };
    
  • 仅有声明的结构体是不完整的类型(incomplete type),不完整类型可以用来定义对应的指针,但不能用来定义对应的对象
    struct Str;
    
    Str * str1;
    
  • 结构体(类)的一处定义原则:翻译单元级别

接下来我们需要了解关于数据成员(数据域)的声明与初始化

  • (C++11)数据成员可以使用decltype来声明其类型,但不能使用auto
    struct Str	
    {	// 结构体内数据成员的声明
    	int x;
        int y;
    };		// 结构体的定义
    
    struct Str
    {
        decltype(3) x;
        // auto x = 3; 非法
        int y;
    };
    
  • 数据成员声明时可以引入const、引用等限定
    struct Str
    {
        const int x = 3;
        int y;
    };
    
  • 数据成员会在构造类对象时定义
    Str str1;		// 此时实现了结构体内数据成员的定义
    
  • (C++11)类内成员初始化
    struct Str	
    {	
    	int x = 3;		// 注意这里仍然是声明而不是定义,并不会在此时为x分配内存并进行关联
        int y;
    };		
    
  • 聚合初始化:从初始化列表指派初始化器
    struct Str
    {
        int x;
        int y;
    };
    
    int main() {
    
        Str m_str {3,4};	// 聚合初始化
        return 0;
    }
    
    但聚合初始化的顺序与结构体内数据的定义顺序有关,这无疑会在一定程度上导致BUG的发生,所以在C++20之后引入了指派初始化器
    struct Str
    {
        int x;
        int y;
    };
    
    int main() {
    
        Str m_str {.x = 3,.y = 4};	// 指派初始化器
        return 0;
    }
    

接下来我们来了解一下mutable关键字

	struct Str
	{
	    mutable int x = 0;			// 标识x可以被修改,可以绕过const限制,在保证常量特性的情况下修改结构体中某些数据成员的值
	    int y;
	};
	
	int main() {
	
	    const Str m_str ;
		m_str.x = 3;
	    return 0;
	}

在结构体中,有一类特殊的数据成员-静态数据成员,它是多个对象之间共享的数据成员。关于静态数据成员,需要关注以下几点

  • 定义方式的衍化
    1. C++98:类外定义,const静态数据成员可以在类内初始化
      struct Str{
          static int x ;
      };
      
      int Str::x = 3;
      
      int main() {
          Str str;
          std::cout << str.x << std::endl;
      }
      
      struct Str{
          const static int x  = 3;
      };
      
      
      int main() {
          Str str;
          std::cout << str.x << std::endl;
      }
      
    2. C++17:内联静态成员的初始化
      struct Str{
          inline static int x = 3;
      };
      
      
      int main() {
          Str str;
          std::cout << str.x << std::endl;
      }
      
  • 可以使用auto推导类型
    struct Str{
        const static auto x = 3;
    };
    
    
    int main() {
        Str str;
        std::cout << str.x << std::endl;
    }
    

对于静态数据成员,我们有以下几种访问方式:

  • .->操作符:针对对象
  • :::针对结构体本身

最后,我们需要知道可以在类的内部声明相同类型的静态数据成员

struct Str{
    static Str x;
};

Str Str::x;			// 定义

int main() {
    Str str;
}

成员函数(方法)

我们可以在结构体内定义函数,作为其成员的一部分:对内操作数据成员,对外提供调用接口。

struct Str{
    int x = 3;
    
    void fun(){
        std::cout << x << std::endl;
    }
};

在结构体中将数据和数据相关的成员函数组合在一起形成类,这是C++在C基础上引入的概念,关于类我们需要关注以下几点

  • 关键字class
    class Str{
        int x = 3;
    
        void fun(){
            std::cout << x << std::endl;
        }
    };
    

    类与结构体的最大区别在默认的成员访问权限上,这个在后续进行讨论

  • 类本身可视为一种抽象数据类型,通过相应的接口(成员函数)进行交互
  • 类本身可形成一个域,称为类域

接下来我们需要关注成员函数的声明和定义

  • 类内定义(隐式内联)
    class Str{
        int x = 3;
    
        void fun(){
            std::cout << x << std::endl;
        }
    };
    
  • 类内声明,类外定义
    class Str{
    public:
        int x = 3;
        void fun();
    };
    
    void Str::fun() {
    
    }
    
  • 类与编译期的两遍处理
    class Str{
    public:
        void fun(){						// 第一遍处理函数的声明(也就是知道了存在这么一个函数)
            std::cout << x << std::endl;// 第二遍处理函数的逻辑
        }
        int x = 3;						// 第一遍处理成员变量的声明
    };
    
  • 成员函数与尾随返回类型(trail returning type)
    class Str{
    public:
        using MyRes = int;
        MyRes fun();
        int x = 3;
    };
    //MyRes Str::fun(){				// 不合法
    //return x;
    //}
    
    //Str::MyRes Str::fun(){		// 合法单写起来比较冗余
    //    return x;
    //}
    
    auto Str::fun() -> MyRes 		// 清晰明了的写法(主要逻辑是从左到右解析到Str这个域名称后会扩大对MyRes的名称的搜索域)
    {
        return x;
    }
    

之后我们关注成员函数与this指针之间的几个知识点

  • this指针是一个指向常量的指针,它是一个隐式传递的参数,用于指向当前的对象
  • 使用隐式的this指针可以构造基于const的成员函数重载
    class Str{
    public:
        void fun(){
            std::cout << "Str * const this" << std::endl;
        }
        void fun() const
        {
            std::cout << "const Str * const this " << std::endl;
        }
        int x = 3;
    };
    
    
    int main() {
        Str str1;
        const Str str2;
        str1.fun();
        str2.fun();
        // 输出结果为
        // Str * const this
    	// const Str * const this 
    }
    

接下来我们来讨论成员函数的名称查找和隐藏关系,需要注意以下几点

  • 函数内部(包括形参)的名称会隐藏函数外部的名称
  • 类内部名称会隐藏类外部的名称
  • 可以使用this或者域操作符来引入依赖型名称查找

除了静态数据成员外,类内也可以包含静态成员函数

class Str{
public:
    static void fun(){
        
    }
    int x = 3;
};

注意,静态成员函数只能操作类的静态数据成员,因为它是属于类的一部分而不是对象的一部分。在静态成员函数中可以返回静态数据成员,一个简单的例子就是单例模式

class Str{
public:
    static auto& instance(){		// 并不是完整的单例模式,只是一个演示
        static Str x;
        return x;
    }
};

最后,成员函数也可以基于引用限定符进行重载(C++11),具体参考这里

基本用不到,用到时候再查吧

访问限定符与友元

C++中可以使用public/private/protected来限定类成员的访问权限

具体的应用会在下一章详细描述

访问权限的引入使得可以对抽象数据类型进行封装,类与结构体的主要区别之一就是缺省访问权限的不同:类为private,结构体为public

我们可以使用友元来打破访问权限的限制-friend关键字,关于友元我们需要关注以下几点

  • 可以声明某个类或某个函数是当前类的友元-慎用!!!
    int main();
    class Str{
        friend int main() ;
    private:
        int x = 100;
    };
    
    int main() {
        Str m_str;
        std::cout << m_str.x << std::endl;
    }
    
    
  • 我们可以在类内首次声明友元类或者友元函数
    class Str{
        friend int main() ;		// 注意与之前不同,在这里这句话被认为main函数的首次声明
    private:
        int x = 100;
    };
    
    int main() {
        Str m_str;
        std::cout << m_str.x << std::endl;
    }
    

    注意,如果使用限定名称引入友元并非友元类(友元函数)的声明

    class Str{
    friend int ::main() ;				// 注意这里不会被认为是main函数的首次声明,所以会报错,因为编译器找不到main函数的声明
    private:
        int x = 100;
    };
    
    int main() {
        Str m_str;
        std::cout << m_str.x << std::endl;
    }
    
  • 友元函数可以在类内或者在类外定义(友元类只能在类外),对于类内定义的友元函数会被认为隐藏友元函数,通过常规名称查找无法查找到
class Str{
    friend void fun(){
        Str m_str;
        std::cout << m_str.x << std::endl;
    }
private:
    int x = 100;
};

int main() {
    fun();			// 无法查找到fun的定义
}

隐藏友元的好处是可以减轻编译器的负担,防止误用(友元本质上打破了类的封装,是一种不符合类的本意的行为)。我们可以通过以下方式来改变隐藏友元的缺省行为

  • 在类外声明或者定义函数
  • 实参依赖查找(隐藏友元的正确用法)
    class Str{
        friend void fun(const Str & val)
        {
            std::cout << val.x << std::endl;
        }
    private:
        int x = 100;
    };
    
    int main() {
        Str m_str;
        fun(m_str);
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值