cpp拾遗——构造, new, delete, 静态成员
1. 构造
构造函数的调用
T t(1,2); // 调用 T(int ,int)
T t2 = (1,2); // 调用 T(int)
T t3 = 1; // 调用 T(int)
T t3 = T(1); // 调用 T(int) 构造匿名对象,并将 匿名对象 和符号 t3绑定
T t4 = t3; // 调用 T(T&)
T t5(t4); // 调用 T(T&)
void func(T t);
func(t5); // 调用 T(T&) 构造形参t
class T {
public:
T(int a); // 当定义了构造函数,编译器就不提供构造函数
}
T a; // 所以这里错误
1.1 构造函数初始化列表
当使用组合对象时,可能需要使用初始化列表
class A{
public:
A(int a);
}
class B {
public:
B(int a, int b);
A m_a;
}
B(1,2); // 这里会报错,由于构造B 时,会先构造A,但是无法调用A的有参构造。
// 所以必须改成
class B{
public:
B(int a, int b) : m_a2(a), m_a(b) {}
A m_a;
A m_a2;
}
组合对象的构造顺序和定义顺序有关,和初始化列表顺序无关。
析构顺序和构造顺序相反
若类有const属性,则必须用参数化列表进行初始化。
class A{
public:
A(int a) :m_a(a) {}
private:
const int m_a;
};
2. 匿名对象
函数返回对象时,会构造一个匿名对象,匿名对象是否被析构,取决于是否又符号去绑定。
T f() {
T a;
return a; // 调用 T(T&) 构造匿名对象
}
T t1 = f(); // 匿名对象又符号接,所以不会被析构
T t2;
t2 = f(); // 匿名对象没符号接,会被析构
3. new delete
3.1 基本用法
int *a = new int;
int *arr = new int[5];
delete a;
delete []arr;
3.2 new delete 和 malloc free
new delete 调用时意味构造析构调用,
对于基础类型,new delete 和 Malloc free 相同,所以可以交叉调用,
对于自定义类型,不能交叉调用。
4. 静态成员
4.1 基本使用
class T{
public:
// 函数或属性 使用 static 声明,则为静态类型
static int g_num;
// 静态成员函数内只能使用 静态成员属性,不能使用非静态成员属性
static int get_num()
{
return g_num;
}
};
// 静态成员属性存储在 .data 段,所以必须 在编译时 分配空间
int T::g_num =1;
4.2 静态成员的本质
使用如下代码反汇编
4 class T{
5 public:
6 static int g_num;
7 static int get_num()
8 {
9 return g_num;
10 }
11 };
12
13 class A{
14 public:
15 static int g_num;
16 static int get_num()
17 {
18 return g_num;
19 }
20 };
21
22 int T::g_num =1;
23 int A::g_num =1;
24 int g_num = 1;
25
26 static int get_num()
27 {
28 return g_num;
29 }
查看 g_num, T::g_num, A::g_num
>> 173 08049af4 g O .data 00000004 _ZN1T5g_numE
>> 189 08049afc g O .data 00000004 g_num
>> 190 08049af8 g O .data 00000004 _ZN1A5g_numE
>> 166 08048678 l F .text 0000000a _ZL7get_numv
>> 175 08048771 w F .text 0000000a _ZN1T7get_numEv
>> 197 0804877b w F .text 0000000a _ZN1A7get_numEv
说明 编译器对符号进行重命名以实现封装
T::g_num --> _ZN1T5g_numE
A::g_num --> _ZN1A5g_numE
g_num --> g_num
T::get_num() --> _ZN1T7get_numEv()
A::get_num() --> _ZN1A7get_numEv()
get_num() --> _ZL7get_numv()
所以以上cpp可用c描述成
22 // class T
23 static int T_g_num = 1;
24 static int T_get_num()
25 {
26 return T_g_num;
27 }
28
29 // class A
30 static int A_g_num = 1;
31 static int A_get_num()
32 {
33 return A_g_num;
34 }
35
36 // normal
37 static int g_num = 1;
38 static int get_num()
39 {
40 return g_num;
41 }
结论:
静态成员 本质 是全局函数和变量,无需定义对象,就能可以直接使用。
由于被cpp编译器重命名,所以必须用 加类前缀的方式调用。
4.3 静态成员函数中为什么不能使用普通成员
静态成员函数中使用 普通成员 会报错,如下
13 class A{
14 public:
15 static int g_num;
16 static int get_num()
17 {
18 f();
m_a++;
19 return g_num;
20 }
21 void f() { get_num(); m_a++; }
void f2() {f(); }
int m_a;
22 };
因为编译时普通成员没有被分配内存。
如上若翻译成c
16 static int A_get_num()
17 {
18 A_f(this); // 错误,因为 A_get_num 没有this变量
this->m_a++; // 错误
19 return A_g_num;
20 }
21 void A_f(A *this) { A_get_num(); this->m_a++; } // 正确
void A_f2(A *this) {f(this);} // 正确
5. 面向对象实现本质
class A{
public:
static int g_num;
static int get_num() {
return g_num;
}
int f() {
return m_a;
}
private:
int m_a;
};
int A::g_num =1;
int main()
{
A::get_num();
A a;
a.f();
return 0;
}
翻译成 c
struct A{
int m_a;
};
static int A_g_num;
static int A_get_num() {
return A_g_num;
}
int A_f(struct A *this) {
return this->m_a;
}
int main()
{
A_get_num();
struct A a;
A_f(&a);
return 0;
}