类描述看上去很像是包含成员函数以及public和 private可见性标签的结构声明。实际上,C++对结构进行了扩展,使之具有与类相同的特性。它们之间唯一的区别是,结构的默认访问类型是public,而类为privatc。C++程序员通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象(常被称为普通老式数据(POD,Plain Old Data)结构)。
一、实现类的成员函数
成员函数的定义与常规函数定义非常相似,他们都有函数头和函数体,也有返回类型和参数,但是他们还有两个特殊的特征
-
定义成员函数是,使用作用域解析符(::)来标识函数所属的类
-
类方法可以访问类的private组件
void Person::add(int a,int b);
1.1、内联方法
定义位于类声明中的函数都将自动成为内联函数,类声明尝尝将短小的成员函数作为内联函数。
如果愿意也可以在类声明之外定义成员函数,并使其成为内联函数,为此,只需要在类实现部分中定义函数时使用inline限定符即可。
class Stock {
private:
void set_lot();
}
inline void Stock::set_lot() {
...
}
内联函数的特殊规则要求在每个使用它们的文件中都对其进行定义。确保内联定义对多文件程序中的所有文件都是可用的、最简便的方法是:将内联定义放在定义类的头文件中(有些开发系统包含智能链接程序,允许将内联定义放在一个独立的实现文件)。
二、构造函数
构造函数是与类名相同的函数,在生成对象时,系统会自动调用构造函数来生成对象。构造函数可以有参数也可以无参数,所以分为有参构造和无参构造。
#include <iostream>
#include <string>
class D2 {
private:
/* data */
double width;
double length;
public:
D2(double width_1,double length_1);
~D2();
void get(void);
};
// 构造函数,有参构造
D2::D2(double width_1,double length_1)
{
width = width_1;
length = length_1;
}
// 析构函数
D2::~D2()
{
}
void D2::get(void) {
std::cout << width << std::endl;
std::cout << length << std::endl;
}
int main(int argc, char* argv[]) {
D2 d2(10,20);
d2.get();
}
2.1、成员名和参数名
不熟悉构造函数的您会试图将类成员名称用作构造函数的参数名,如下所示:
Stock::Stock(const string & company){
company = company
}
这是很明显错误的,为了避免这种混乱,一种常见的做法是在数据成员名中使用m前缀,或者使用后缀
2.2、默认构造函数
默认构造函数是在未提供显式初始值时,用来创建对象的构造函数。也就是说,它是用于下面这种声明的构造函数:
Stock stock;
不含任何成员变量的初始化。
奇怪的是,当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义了构造函数后,程序员就必须为它提供默认构造函数。如果提供了非默认构造函数(如Stock(const char * co, int n, doublepr)),但没有提供默认构造函数,则下面的声明将出错:
Stock stock;
这样做的原因可能是想禁止创建未初始化的对象。然而,如果要创建对象,而不显式地初始化,则必须定义一个不接受任何参数的默认构造函数。定义默认构造函数的方式有两种。一种是给已有构造函数的所有参数提供默认值:
Stock stock(const string & company = "hust");
另一种方式是通过函数重载来定义另一个构造函数,一个没有参数的构造函数
Stock::Stock();
由于只能有一个默认构造函数,因此不要同时采用这两种方式。实际上,通常应初始化所有的对象,以确保所有成员一开始就有己知的合理值。因此,用户定义的默认构造函数通常给所有成员提供隐式初始值。例如,下面是为Stock类定义的一个默认构造函数:
Stock::Stock(){
company_ = "hust"
}
在设计类时通常应该对所有的参数进行初始化。
使用任何一种方式创建默认构造参数后都可以声明函数变量,而不对其进行显式初始化
Stock stock;
Stock stock = Stock();
Stock *stock = new Stock;
整体可见下面的程序
#include <iostream>
#include <string>
class Stock{
private:
std::string company_;
public:
Stock(const std::string & company);
Stock();
std::string get_company(){
return company_;
}
};
Stock::Stock(const std::string &company) {
company_ = company;
}
Stock::Stock() {
company_ = " ";
}
int main() {
std::string str = "123";
Stock stock1 = Stock(str);
Stock stock2 = Stock();
std::cout << stock2.get_company() << std::endl;
std::cout << stock1.get_company() << std::endl;
return 0;
}
2.3、析构函数
析构函数在对象即将被销毁时完成一些清理工作,这其实是非常有必要的,比如说我们在对象的生命周期内使用了new来在堆中占用一块内存,在析构函数中我们需要将他delete掉。
class Stock{
private:
std::string company_;
public:
Stock(const std::string & company);
Stock();
// 析构函数
~Stock();
}
2.4、C++11列表初始化
Stock stock = {"hust"};
Stock stock {};
分别对应了有参构造和无参构造。
C++11允许您进行更加直观的初始化
class Box{
int length = 10;
int width = 20;
}
这和在构造函数中使用成员初始化列表等价
Box::Box() : length(10),width(20) {}
2.5、const成员函数
请看下面的代码
const Stock land = Stock("neu");
land.show();
编译器拒绝执行第二行,为什么呢,因为show()的代码无法保证调用对象不被修改,调用对象和const一样,不应该被修改。我们以前通过将函数参数声明为const引用或指向const的指针来解决这种问题,但是这里存在语法问题,show方法没有任何参数,相反,他使用的方法是由方法调用隐式提供的,需要一种方法确保函数不会修改调用对象,C++的解决方法是将const关键字放在函数的括号后面。也就是这样
void show() const;
同样。函数定义的开头应该像这样
void Stock::show() const;
以这种方式声明和定义的类函数被称为const成员函数。就像应尽可能将const引用和指针用作函数形参一样,只要类方法不修改调用对象,就应将其声明为const。从现在开始,我们将遵守这一规则。
三、使用初始化列表来初始化字段
我们这样的话有参构造需要一个一个的填写参数,所以就有了初始化字段
#include <iostream>
#include <string>
class D2
{
private:
/* data */
double width;
double length;
public:
D2(double width_1,double length_1);
~D2();
void get(void);
};
// 这种写法和上面的完全一样
D2::D2(double width_1,double length_1):width(width_1),length(length_1)
{
}
D2::~D2()
{
}
void D2::get(void) {
std::cout << width << std::endl;
std::cout << length << std::endl;
}
int main(int argc, char* argv[]) {
D2 d2(10,20);
d2.get();
}
四、类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
#include <iostream>
#include <string>
class D2
{
private:
double width;
double length;
public:
D2(double width_1,double length_1);
~D2();
void get(void);
};
D2::D2(double width_1,double length_1):width(width_1),length(length_1)
{
std::cout << "我被创建了" <<std::endl;
}
D2::~D2()
{
std::cout << "我被删除了" <<std::endl;
}
void D2::get(void) {
std::cout << width << std::endl;
std::cout << length << std::endl;
}
int main(int argc, char* argv[]) {
D2 d2(10,20);
d2.get();
return 0;
}
五、作用域为类的常量
您可能认为下面的代码可以实现
class Back{
private:
const int Months = 12;
}
这是行不通的,因为声明类只是描述了对象的形式,他并没有创建对象,在创建对象之前并没有能用于存储值的空间,然而有两种方式可以实现这个目标,并且效果相同。
5.1、声明一个枚举
class Back{
private:
enum {Months = 12};
}
用这种方式声明的枚举并不会创建类数据成员,也就是说所有的对象中都不包含枚举,另外Months只是一个富豪名城,在作用域为整个类的代码中遇到他,编译器将会用数字替换他。
5.2、使用static
class Back{
private:
static const int Months = 12;
}
这将创建一个名为Months 的常量,该常量将与其他静态变量存储在一起,而不是存储在对象中。因此,只有一个Months 常量,被所有Back对象共享。在C++98中,只能使用这种技术声明值为整数或枚举的静态常量,而不能存储double常量。C+11消除了这种限制。
六、类作用域内枚举
传统的枚举存在一些问题。其中之一是两个枚举定义中的枚举量可能发生冲突。假设有一个处理鸡蛋和T恤的项目,其中可能包含类似下面这样的代码:
enum egg {Small,Medium,Large,Jumbo};
enum t_shirt {Small,Medium,Large,Jumbo,Xlarge}
这将无法通过编译,因为egg Small和t_shirt Small位于相同的作用域内,它们将发生冲突。为避免这种问题,C++11提供了一种新枚举,其枚举量的作用域为类。这种枚举的声明类似于下面这样:
enum class egg {Small,Medium,Large,Jumbo};
enum class t_shirt {Small,Medium,Large,Jumbo,Xlarge}
调用的时候我们只需要
egg::Large;
t_shirt::Small;
七、类的静态成员
静态成员有一个特点,无论创建了多少对象,程序都只创建一个静态类变量副本,也就是说,所有的对象都共享一个静态成员。这对于多有类对象都具有相同值的死有对象是很方便的。
请注意,不能在类的声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但是并不分配内存,你可以用这种格式来创建对象,从而分配内存和初始化内存,对于静态成员,可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分,请注意,初始化语句指出了类型,并使用了作用域运算符,但是没有使用关键字static。
初始化是在方法文件中,而不是类声明文件中进行的,这是因为类声明文件位于头文件中,程序可能将头文件包含在其他的几个文件中,如果在头文件中进行初始化,将出现多个初始化语句副本,从而引发错误。
对于不能再类声明中初始化静态数据成员的一种例外是静态数据成员是整型或者枚举型const。正如五和六所示。
八、类的静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}