C++学习系列文章均翻译自learncpp.com,一个非常好的C++学习网站,这个网站让我领悟到原来深奥的道理也可以讲的如此浅显易懂, 如果所有的软件都有类似的网站该多好啊,为了加深印象,我决定逐章翻译一下这个网站,哎,我自己都不相信我能做到...
C++提供了一些基本的数据类型(例如:char, int, long, float, double, 等等...),这些类型在处理相对简单的问题时通常是可以满足需要的,但是仅仅使用这些类型在解决复杂的问题时就会比较困难。C++的一个常用的特性就是可以自定义数据类型,以便更好的对应需要解决的问题。在之前的章节你已经看到了如何使用枚举及结构来创建你自己的数据类型。
下面是一个用结构来存储数据的例子:
struct DateStruct{ int year; int month; int day;};
枚举类型及只包含数据的结构(结构里只包含变量)代表了传统的非面向对象编程的世界,因为他们只保存数据。在C++11中,我们可以按如下方式创建并初始化该结构:
DateStruct today { 2020, 10, 14 } ; // use uniform initialization
现在,如果我们希望在屏幕上打印数据的内容(我们可能经常会执行的操作),可以写一个函数来完成这个工作。全部代码如下:
#includestruct DateStruct{ int year; int month; int day;};void print(DateStruct &date){ std::cout << date.year << "/" << date.month << "/" << date.day;}int main(){ DateStruct today { 2020, 10, 14}; // use uniform initialization today.day = 16; // use member selection operator to select a member of the struct print(today); return 0;}
这段代码将输出如下内容:
2020/10/16
类
在面向对象编程的世界里,我们通常期望我们的数据类型不只储存数据,而是同时包括处理这些数据的函数。在C++中,这种类型的变量通过class关键字来声明。用class关键字可以定义一个被称为类的新的用户自定义类型。
在C++中,类与只包含数据的结构很像,除此之外类还提供了更强的功能及灵活性。事实上,下面的结构与类实际上是相同的:
1 struct DateStruct 2 { 3 int year; 4 int month; 5 int day; 6 }; 7 8 class DateClass 9 {10 public:11 int m_year;12 int m_month;13 int m_day;14 };
请注意最大的区别是class中的public关键字。我们将在下一节中讨论这个关键字。
和声明一个结构相同,声明一个类并不会分配任何内存。它只是定义了类看起来是什么样子的。
警告:像结构一样,在C++中最容易犯的一个错误就是在类声明结束时忘记打分号。这将导致一个指向下一行的编译错误。最新的编译器比如Visual Studio 2010会给你一些指导,告诉你你可能忘记了分号,但是较早的或功能不够强大的编译器可能不会给出这些提示,这将导致很难发现真正的错误所在。
和声明一个结构一样,必须要声明一个改class类型的变量:
1 DateClass today { 2020, 10, 14}; // declare a variable of class DateClass
在C++中当我们定义一个class类型的变量时,我们称之为类的实例化。这个变量被成为该类的一个实例。一个class类型的变量也被称为一个对象。像定义一个内部变量(例如:int x)时系统会为该变量分配内存,实例化一个对象(例如:DateClass today)时系统也会为该对象分配内存。
成员函数
除了包括数据外,类也可以包括函数。class中定义的函数被称为成员函数。下面的例子中我们的Data类包括了一个用来打印日期的成员函数:
1 class DateClass 2 { 3 public: 4 int m_year; 5 int m_month; 6 int m_day; 7 8 void print() // defines a member function named print() 9 {10 std::cout << m_year << "/" << m_month << "/" << m_day;11 }12 };
像结构的成员一样,类的成员(变量及函数)通过(.)操作符访问:
1 #include2 3 class DateClass 4 { 5 public: 6 int m_year; 7 int m_month; 8 int m_day; 9 10 void print()11 {12 std::cout << m_year << "/" << m_month << "/" << m_day;13 }14 };15 16 int main()17 {18 DateClass today { 2020, 10, 14 };19 20 today.m_day = 16; // use member selection operator to select a member variable of the class21 today.print(); // use member selection operator to select a member function of the class22 23 return 0;24 }
运行结果:
2020/10/16
你可能注意到上面的代码和我们之前使用结构的版本多么的相似。
但是,也有一些区别。在使用结构的版本中,我们需要把结构本身作为一个参数传递给print()函数。否则的话print()函数不知道我们要处理的是哪个DateSturct结构。
但是,在我们类版本的print()中,我们不需要传递要打印的DataClass!因为print()是通过class类型的变量"today"调用的,print()函数中的成员变量将指向class类型的变量today!于是,在today.print()函数内部,m_day实际上指向了today.m_day。如果我们调用tomorrow.print(),print()中的m_day将指向tomorrow.m_day.
成员变量使用m_作为前缀可以帮助区分开成员变量与函数形参或本地变量。这样做的好处有几个。第一,当我们看到给m_作为前缀的变量赋值时,我们就知道我们在改变类的状态。第二,与在本地定义的函数形参及本地变量不同,成员变量是在类的内部定义的。于是如果我们想查看以m_开头的变量的类型定义,我们不能在函数内部找,而是要在类的定义中找。
按照惯例,类的名称的首字母应该大写。
规则:定义类时首字母大写
下面是定义类的另一个例子:
1 #include2 #include 3 4 class Employee 5 { 6 public: 7 std::string m_name; 8 int m_id; 9 double m_wage;10 11 // Print employee information to the screen12 void print()13 {14 std::cout << "Name: " << m_name <<15 " Id: " << m_id << 16 " Wage: $" << m_wage << '\n'; 17 }18 };19 20 int main()21 {22 // Declare two employees23 Employee alex { "Alex", 1, 25.00 };24 Employee joe { "Joe", 2, 22.25 };25 26 // Print out the employee information27 alex.print();28 joe.print();29 30 return 0;31 }
输出结果如下:
Name: Alex Id: 1 Wage: $25
Name: Joe Id: 2 Wage: $22.25
和一般的函数不同,成员函数的定义顺序没有影响!
关于C++中的结构
在C语言中,structs只能保存数据,而不能包含成员函数。在C++语言中,在设计完class之后,Bjarne Stroustrup花了一段时间考虑是否structs(继承自C语言)也应该被赋予相同的功能。经过一番考虑,他决定他们应该具有相同的规则集。所以虽然我们在写上面的程序时使用了class关键字,但是我们也可以使用struct关键字来代替。
许多开发者(包括我自己在内)感觉这是一个错误的决定,因为它可能导致危险的假设:例如,假设一个class会回收自己是合理的(比如一个class会在失效后释放其分配的内存),但是假设sturct也会这样做是不安全的。相应的,我们建议struct关键字使用在只有数据的场合,而class关键字用来定义需要包括数据及函数的对象。
规则:使用struct关键字在只有数据的结构。使用class关键字定义包括数据和函数的对象。
结论
Class关键字让我们可以在C++中创建包括成员变量和成员函数的自定义类型。Class构成了面向对象编程的基础,我们将在本章之后的内容及之后的许多章节来探索class必须提供的功能。