語言/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
里调用该函数 可能结果是aaa
即B.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就是一個單獨的類;