語言/CPP {类class,内部类}

語言/CPP {类class,内部类}

@LOC_2

类class

错误

过去一直以为: 类的作用域 是当前cpp, 也就是 他不是整个程序的命名空间, 即:

A.cpp:  struct ST{ int a = 123;}
B.cpp:  struct ST{ string b = "abc";}

他虽然编译没有问题, 但是 他会导致莫名其妙的错误!
比如, 你在A.cpp里 输出ST().a , 在b.ccp: 输出ST().b (假设main.cpp和A.cpp在一个文件里) 编译命令是A.cpp B.cpp -o, 那么 你会发现 b.cpp里的 ST().b 并不等于"abc";
. 会导致的错误 非常多, 比如你在两个类里面 同时写static constexpr int A 但赋值不同的初值 你会发现B.cpp里 调用的其实是A.cpp里的定义; 再比如 两个类同时写void F(){ 不同的逻辑} B.cpp里调用的 也是A.cpp里的定义;
. 再比如 两个类同时写static int D; 然后再定义int ST::D = ?;, 此时你会发现 连编译都过不了了, 因为int ST::D这个符号 是全局的, 两个文件里都有int ST::D = ? 就重定义了;

因此综上, 实际上 类的作用域 是全局的, 只不过 虽然允许多文件重复定义同一个类 但是你要保证他俩的内容 是一模一样的 否则他会使用第一次的定义 (比如A.cpp: struct ST{ void F(){ aaa}}; B.cpp: struct ST{ void F(){ bbb;}}, 那么 最终ST::F()这个函数 他要么是aaa 要么是bbb 他是全局的 取决于谁先注册; 因为成员函数本质上还是全局函数);

因此 对于非模板类, 一定要保证 他的声明和实现 是分离的! 否则的话, 你就重复注册了, 比如 即使内容一致 即都是void F(){ xxx}, 但他是会重复注册这个函数; 最好就是声明和实现分离, 这样 每个.cpp里使用的struct ST, 他的声明都是一致的; 即不要在.hpp里面 直接实现函数;
. 但是你看QT源码, 会发现 他里面的非模板类的 有些非模板函数 会直接写实现, 比如int QTimer::timerId() const { return id; }, 大概是因为 他很简单 会自动inline? 大多数直接实现放头文件里的 会满足: {模板函数; inline函数(此时代码很短 即函数逻辑极其简单)}; 但即使如此, 我认为 只要是非内联的函数, 你只要在头文件里实现, 他就是会重复注册的 (因此你必须保证 他们的实现代码是完全一样的); 其实重复注册 是没问题的, 但感觉还是不太好…

总之, 最好就是: 非模板类,声明放hpp, 实现放cpp;

{ 以下说明是错误的, 模板函数/模板类的所有函数 都必须放到头文件里
这点 和 模板函数 是一样的,假如A.cpp, B.cpp里 都是一个template< class _T_> void F( _T_)函数 但他俩的实现内容不同,那么 此时也会导致重复注册 (比如A.cpp里是 aaa; B.cpp里是bbb, 那么B.cpp里调用该函数 可能结果是aaaB.cpp的bbb没有注册成功;
}

性质

class ST{
	using T0_ = int;
	class T1_{};
	
	T0_ Func( T1_){ cout<< Data;}
	
	int Data;
};

注意结构, T0,T1的定义式 必须在Func上面 (与private/public无关); 而Data数据 可以放在下面 没问题;

@DELI;

const T a;, 此时他肯定不能访问非const的函数; 但是 他依然可以访问非const变量; 即他可以修改a.data 这是可以的;
为啥这样设计呢? 其实没办法, 因为一个变量 他既可以是右值(只访问) 也可以是左值(修改);
比如const对象只是访问非const变量, 不会进行修改 (虽然实际上他也可以修改);

@DELI;

成員變量進行賦值操作, 即int data = 123, 其實就相當於是 在他的構造函數的初始化列表裡 有一個: data(123);

@DELI;

#構造|析構函數, 是在運行時執行的# @MARK: @LOC_0;
你在全局變量上ST s, 雖然他的內存 在ELF文件裡已經確定了, 但 在編譯期 這是確定了她的內存空間, 而她內存空間的初始化 可沒有確定 可以認為就是隨機值, 即 編譯期是不會去調用構造函數的, 她的初始化 即執行構造函數, 是在運行時調用的!
同樣 全局變量的析構函數, 也是在運行時執行的;
. 這兩點, 你可以通過在OJ平台上, 測試他的運行時間 而證實; 即OJ判斷運行時間 是指的整個程序的運行時間, 而不是main函數的運行時間, 因此 全局數組/動態new空間, 其實差不多 都是佔運行時間的;

@DELI;

如果你不寫構造函數, 那麼最好對你的成員變量 進行初始化賦值操作(无论类成员变量/全局变量 都是支持定义时就直接赋值) 尤其是指針;
class A{ T * root = nullptr;} 這就等價於是構造函數, 即你的對象 她的root == nullptr, 否則的話 她是未知的 那麼你析構函數 if( root == nullptr) delete root; 就是錯誤的 (因為他是野指針), 導致Segmentation error;

@DELI;

#構造函數遞歸#

class ST{
public:
	int data;
    ST() : data(0){ xxx}
    ST( int a) : ST(){ ...}
    ST( double a) : ST(){ ...}
};

比如對於ST(int) 先執行data(0) 然後執行xxx 最後執行...;
注意此時ST(int/double)不可以再進行初始化列表, 即ST(int) : ST(), data(a)是錯誤的;

@DELI;

类分为{平凡/非平凡}类型, 只要你有自定义的构造函数 那么他就是非平凡类型(编译器不会自动生成默认构造了);

@DELI;

#类的普通成员变量的默认初始化#

class T{
public:  int a;  string b;  double * ptr;
};
class ST{
public:
    int a = {1};
    string b = {"abc"};
    T c = {123, "hh", &D};
};

这是C++的新语法;
#以前#: 只有静态变量 才能直接赋予初值, 而对于(普通成员变量)的初始化 只能放到构造函数里面; 可是这有个问题, 假如你的构造函数ST()很多 而他们对a,b,c都是进行的完全相同的初始化, 那么你就需要在每个ST()构造函数里 对abc进行完全相同的初始化, 这就很麻烦 造成了很多重复代码;
#现在新语法#: 在构造函数之前 可以先进行对成员变量的默认初始化 构造函数就不用管这个工作了 (当然构造函数仍然可以对他进行初始化, 如果构造函数不管 那他的值就是默认初始化, 否则他的值 就根据构造函数的初始化);
但是注意, 这种默认初始化 是个新语法, 他并不完全是(构造函数); 比如 你上面写成string b("abc");这就错误了 但是他确实对应着一个string( const char *)的构造函数; 比如对于T c = {123, "hh", &D}这个语法, 如果你此时添加了T(){}构造函数 此时T c = ...这个语法就报错了(说明编译器之前自动生成了T的默认构造函数 而现在不自动生成了) 你此时只能再手动添加T( int, string, double*)的构造函数 才是正确的;
. 换句话说, T c = {123, "hh", &D}其实就是对应一个T(int,string,...)的构造函数(编译器自动生成的), 但是在语法上 你不能写成T c(123,"hh",..); 因为这个语法是调用函数(这是在运行时的语法 而T c所在的域不是函数域);
. 这个规则 与(初始化列表方式来申请类对象)是一样的, 下面会讲到;
#初始化列表方式来申请类对象#
在函数里 申请一个对象T c = {123, "hh", &D};这是正确的, 也可以把=去掉; (他的前提也是 要么T没有自定义构造函数, 要么T必须有(int, string, double*)的构造函数);
. 常见应用如vector<int> A = {1,2,3,...};; (因为vector有自定义构造函数, 因此这说明vector一定有一个形如(initialize_list<int>)的构造函数 来接收这个{1,2,3...});
. 还有一个特殊语法: T c = {.c = &D, .a = 123}; (即对a,c初始化 不管b), 但这语法的前提很严格, 此时T必须没有(自定义构造函数), 假如T有构造函数(不管是什么) 这个语法都是错误的;

@DELI;

成员函数 能加const就加上去!
因为函数传递类对象时 通常是const ST &, 这个对象 他只能调用const函数; 比如你一个函数 他本身是const的 结果你没写const 这就导致(本来他是应该能被const对象所调用的 现在却不能);

內部類

性質

內部類的本質 其實就是普通類, 唯一區別就是 她的名字其實有一個前綴XX::;

class A{
    int a;
    class B{ void F(){ cout<< a;}}
};

這個代碼是錯誤的, B的函數 是訪問不到A.a的, 你就想象 B就是一個單獨的類;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值