类实际上就是我们自己定义的一种数据类型,本质上来讲它和int,float什么的没有什么不同。

在C语言中,我们一般用struct来定义我们自己的类型,在C++中我们引入了类的概念,一般用class来定义自己的类型。实际上这两个概念很像,只不过class多了一些东西,比如可以定义private的类型等等。

构造函数

类需要一个构造函数来进行类的初始化,一般来讲,当我们new一个类的时候,会首先调用它的初始化函数来对类中的成员进行初始化。

构造函数的名字与类相同,并且没有返回值,也没有返回类型。在很多类中我们看不到构造函数,是因为编译器会隐式给类默认生成构造函数。但是在实际中我们还是应该显示的定义构造函数,这是因为有些场景下编译器生成的默认构造函数是有问题的,或是会报错的,或是在有些类中无法生成默认构造函数。

1
2
3
4
5
6
7
8
9
10
class Sales_data {
Sales_data() = default;
Sales_data(const std::string &s): bookNo(s) {}
Sales_data(const std::string &s, unsigned n, double p) :
bookNo(s), unit_sold(n), revenue(p) {}
Sales_data(std::istream &);
std::string bookNo;
unsigned int unit_sold = 0;
double revenue = 0.0;
};

上面定义了一个类,这个类有三个构造函数。

1
Sales_data() = default;

这个的意思是告诉编译器要为我们生成一个默认的构造函数,如果不这样,那么因为我们定义了构造函数,那么编译器就不会为我们生成默认的构造函数了。

剩下的两种就是我们自己定义的构造函数,这宗定义的方式我们称之为构造函数初始值列表。

当然我们还两外的构造方式,如下所示,这就跟普通的函数是一样的了。

1
2
3
Sales_data::Sales_data(std::istream &is) {
read(is, *this);
}

访问控制和封装

前面说到struct和class的不同就在于class里面可以进行访问权限控制

public:整个程序都可以使用,一般用来定义接口

private:只能被类的成员进行访问,一般用来定义类的成员以及私有的实现。

所以我们可以修正一下上面的类定义

1
2
3
4
5
6
7
8
9
10
11
12
class Sales_data {
public:
Sales_data() = default;
Sales_data(const std::string &s): bookNo(s) {}
Sales_data(const std::string &s, unsigned n, double p) :
bookNo(s), unit_sold(n), revenue(p) {}
Sales_data(std::istream &);
private:
std::string bookNo;
unsigned int unit_sold = 0;
double revenue = 0.0;
};

友元

由于类中的private成员不能让外部访问,所以如果我们需要外部的类或者函数来修改private成员的话,就没有办法了,因此我们定义了友元的概念。

类允许其他的类或者函数访问它的非共有成员,方法是其他类或者函数成为它的友元,用friend关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Sales_data {
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
public:
Sales_data() = default;
Sales_data(const std::string &s): bookNo(s) {}
Sales_data(const std::string &s, unsigned n, double p) :
bookNo(s), unit_sold(n), revenue(p) {}
Sales_data(std::istream &);
private:
std::string bookNo;
unsigned int unit_sold = 0;
double revenue = 0.0;
};

this

this实际上指的是类本身,在某一些场景下我们需要显示的用到this,比如我们要返回类的时候,在下面有例子说明。

类的静态成员

类的静态成员跟类本身有关,而跟它的对象基本无关。
一般用static来定义。

类的静态成员存在与任何对象之外,对象中不包含任何与静态数据成员有关的数据。静态成员也不跟任何对象绑定在一起,他也不包含this指针。

比如说定义一个账户类,那么我们可以将利率定义为static的,那么这个利率就跟类有关,而跟对象无关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Account {
public:
void calculate() {
amount += amount * interestRate;
}
static double rate() {
return interestRate;
}
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
void Account::rate(double newRate) {
interestRate = newRate;
}

在这个类中,所有的Account对象将包含两个数据成员owner和amount。但是只存在一个interestRate对象且被所有的Account对象共享。
我们可以直接用类名或者对象来访问静态成员

1
2
3
double r = Account::rate();
Account r1;
r = r1.rate();

实例说明

我们定义一个Screen类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
char get() const {
return contents[cursor]; // 隐式内联
}
Screen &move(pos r, pos c);
inline char get(pos ht, pos wd) const; // 显式内联
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
char Screen::get(pos r, pos c) const {
pos row = r * width;
return contents[row * c];
}
inline Screen &Screen::move(pos r, pos c) {
pos row = r * width;
cursor = row + c;
return *this;
}

这个类里面首先我们用typedef定义了一个pos,这样就能封装类的一些细节,同时方便理解类。

内联函数

其次我们定义了内联函数,内联函数的有点就不说了,上面的例子是三种内联函数的声明方式。

mutable可变变量

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
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
char get() const {
return contents[cursor]; // 隐式内联
}
Screen &move(pos r, pos c);
inline char get(pos ht, pos wd) const; // 显式内联
void some_member() const;
private:
mutable size_t access_ptr;
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};

char Screen::get(pos r, pos c) const {
pos row = r * width;
return contents[row * c];
}
inline Screen &Screen::move(pos r, pos c) {
pos row = r * width;
cursor = row + c;
return *this;
}
void Screen::some_member() const {
++access_ptr;
}

这里我们添加了一个mutable的变量,用来表示some_member函数的调用次数。

这是因为对于函数名后加const的行为,在这个函数中是不能修改成员遍历那个的,所以必须将相应的成员变量设置为mutable。mutable是可变数据成员,当我们将一个成员变量定义为mutable时,即使这个变量在const成员函数中,我们也可以改变它的值。

1
void some_member() const;

this指针返回对象,用于隐式传递

我们可以从上面的代码中看到我们定义了一个函数,返回了this指针,也就是对象本身的引用。这样做是为什么呢?

1
2
3
4
5
inline Screen &Screen::move(pos r, pos c) {
pos row = r * width;
cursor = row + c;
return *this;
}

这个函数的目的是移动屏幕的光标,那么移动完了就好了,为何还要返回对象的引用呢?这是为了解决一个本质的问题。

当我们移动一下光标后没有问题,那么当我们还需要继续操作呢?这就有问题了。

1
2
Screen myScreen;
myScreen.move(4,0).set('$', 4);

因此返回this引用就是可以实现上面的操作。

假设我们添加了下面的display函数,实际上这个函数不需要改变任何值,那么我们可以将其设置为const类型的,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}

const Screen &display(std::ostream &os) const {
do_display(os);
return *this;
}
private:
void do_display(std::ostream &os) const {
os << contents;
}
mutable size_t access_ptr;
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};

但是这样有个问题,我们如果按照如下的方式调用则没有问题

1
2
Screen myScreen;
myScreen.display(std::cout);

但是如下调用就麻烦了:

1
2
Screen myScreen;
myScreen.display(std::cout).set('*');

这是因为我们的返回值是个const,也就是myScrren是个常量,他是没有办法再进行set的。因此针对这中场景,有一种解决方式,就是基于const的重载,新添加一个非const的函数,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}

Screen &display(std::ostream &os) {
do_display(os);
return *this;
}
const Screen &display(std::ostream &os) const {
do_display(os);
return *this;
}
private:
void do_display(std::ostream &os) const {
os << contents;
}
mutable size_t access_ptr;
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};

这样就解决了上面的两种调用的问题了。

Screen类的友元问题

我们定义一个Window_mgr类,这个类需要调用Screen中的私有成员,这样原则上是不允许的,要想实现,我们就需要将Window_mgr定义为Screen的友元。

1
2
3
4
5
6
7
8
9
10
11
12
class Window_mgr{
public:
using ScreenIndex = std::vector<Screen>::size_type;
void clear(ScreenIndex);
private:
std::vector<Screen> Screens{Screen(24, 80, ' ')};
};

void Window_mgr::clear(ScreenIndex i) {
Screen &s = Screens[i];
s.contents = string(s.height * s.width, ' ');
}

将Window_mgr定义为Screen的友元。注意定义友元之前要先声明

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
class Window_mgr;
class Screen {
friend Window_mgr;
public:
typedef std::string::size_type pos;
Screen() = default;
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
char get() const {
return contents[cursor]; // 隐式内联
}
Screen &move(pos r, pos c);
inline char get(pos ht, pos wd) const; // 显式内联
void some_member() const;
Screen &display(std::ostream &os) {
do_display(os);
return *this;
}
const Screen &display(std::ostream &os) const {
do_display(os);
return *this;
}
private:
void do_display(std::ostream &os) const {
os << contents;
}
mutable size_t access_ptr;
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
char Screen::get(pos r, pos c) const {
pos row = r * width;
return contents[row * c];
}
inline Screen &Screen::move(pos r, pos c) {
pos row = r * width;
cursor = row + c;
return *this;
}
void Screen::some_member() const {
++access_ptr;
}

也可以直接定义友元的函数,而不必要将整个类定义为友元,如下所示:

1
friend void Window_mgr::clear(ScreenIndex);
显示 Gitment 评论