c++中类的相关概念

c++中类的相关概念

1. 类的定义

为什么类的定义后面一定要加“;”?

因为类后面是可以直接定义实例,但是并不建议这样定义,因为可读性比较差,变量最好单独定义。

1
2
3
4
5
// bad idea
class Sales_data {/*....*/} accum, trans, *saleptr;
// batter eay to define objects
class Sales_data {/*....*/};
Sales_data accum, trans, *saleptr;

2. 变量的初始化

变量后面直接跟着相应的数值成为初始化(initialize),是变量刚开始创建的时候,给的一个值。

1
2
3
4
// 可以使用前面的定义
double price = 109.9, discount = price * 0.26;
// 也可以使用函数的结果赋值
double salePrice = applyDiscount(price, discount);

c++中初始化(initialization)与赋值(assignment)是两个不同的概念,初始化是变量创建的时候发生的,赋值是值将变量的值替换。

初始化的方式有四种:

1
2
3
4
int units_sold = 0;
int units_sold(0);
int units_sold = {0};
int units_sold{0};

花括号的方式称为列表初始化,列表初始化,不会对字面量做转化。

1
2
int a{3.14}; // not allowed
int a = 3.14 // ok; value will be trunked

如果没有初始化,则会对变量进行默认初始化(default initializer)。

函数外部的变量会定义为0或空,函数内部的变量则不会进行初始化,未初始化的变量不能进行拷贝与访问内部成员。

  • 类内的成员变量如果没有显式初始化,则进行默认初始化,字符串类型变量为空,整形初始化为0 (GCC编译器并非如此)
  • 类内的成员变量初始化不允许‘()’形式

3. struct与class的区别

struct与class的位移区别是默认访问权限的区别,如果所有成员都默认为public则用struct字段,如果默认为私有则用class。

public定义接口(interface);

private封装(encapsulate)内部成员。

4. 构造函数

构造函数是指与类名相同,没有返回类型,具有参数列表,可以重载的函数。

当且仅当没有定义构造函数,则内部成员变量会进行默认初始化(default initializer)和类内初始化器(in-class initializer),默认初始化在默认构造函数内进行。在gcc上没有复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A {
int a;
int b=2;
public:
void print() {
cout << "a is :" << a << ", b is :" << b << endl;
}
};

int main() {
A a;
a.print();
return 0;
}

结果:a is :-704482032, b is :2

如果自定义构造函数,则所有的内部成员变量都在构造函数内进行初始化。也可以不用

构造函数的几种写法:

1
2
3
4
5
6
struct A {
int a;
A() = default; //默认构造函数
A(int a) {this->a = a}
A(int a):a(a) {} //初始化列表
}

默认构造函数、析构函数对使用了动态内存的程序并不有效。

5. 类型转换

C++中有两种函数可以执行类型转换,一种为单变量的构造函数(或无默认值的参数),一种为类型转换运算符重载。

单变量的构造函数会为编译器提供一种隐式类型转换的方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

class Sale_data {
public:
Sale_data(const std::string str): book(str){
std::cout << "call Sale_data constructor" << std::endl;
}
std::string get_book() {
// std::cout << book << std::endl;
return book;
}
operator double() {
return 0.0;
}

//重载外部操作符 <<
friend std::ostream& operator<< (std::ostream& os, Sale_data a);
private:
std::string book;
};

std::ostream& operator<< (std::ostream& os, Sale_data a) {
os << a.get_book();
return os;
}

class Item {
public:
Item() = default;
void combine(Sale_data a) {
items.push_back(a);
}
void print() {
for (auto i: items){
std::cout << i << "\n";
}
std::cout << std::endl;
}
private:
std::vector<Sale_data> items = {};

};
int main() {
Item items;
Sale_data a = std::string("aaa"); // 正常初始化
items.combine(a); // 正常函数调用
items.combine(std::string("vbb")); //ok, 做了一次隐式类型转换,将string("vbb") 转换为Sale_data对象
//items.combine("aaaa"); //error 只允许一次类型转换,"aaa"不能自动转换为string再转换为Sale_data
items.print();
// a.get_book();
return 0;
}

C++11引入explicit显式声明不允许进行隐式类型转换,例如

1
2
3
4
5
6
7
8
9
10
11
12
class Sale_data {
explicit Sale_data(const std::string str): book(str){
std::cout << "call Sale_data constructor" << std::endl;
}
}

int main() {
std::string a = "aaaa";
Sale_data o = a; // error 不允许, 经实验并没有调用copy constructor,只是单纯的语法不允许
// items.combine(a); //error 不能进行隐式类型转换
items.combine(Sale_data(a)); //ok,显式转换,或者这就是在实例化一个类,生成一个对象
}

explicit还作用域列表初始化语法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Sale_data {
explicit Sale_data(const std::string str, const int a): book(str), no(a) {}
}

void Foo(Sale_data);

int main() {
Sale_data a(std::string("bbb"))
Sale_data o = {std::string("aaa"), 111}; //error
Sale_data o{std::string("aaa"), 111}; //ok
Sale_data o{std::string("aaa"), a}; //ok, 因为我们在上面重载了一个double的类型转换操作符
Foo({std::string("aaa"), 111}); //error
}