您的位置:首页 > 编程语言 > C语言/C++

查漏补缺——类(C++ Primer)

2016-07-07 23:06 495 查看
1、构造函数

(1)编译器可以创建一个合成的默认构造函数,为什么我们还需要显式创建构造函数?

原因有三:

只有在类中没有声明任何的构造函数时,编译器才为类自动生成默认构造函数(如果定义了其他的构造函数,最好还是提供一个默认构造函数)
对某些类来说,合成的默认构造函数可能会执行错误的操作,比如说含有内置类型或符合类型(比如数组和指针)成员的类应该在类内部初始化这些成员,否则可能得到未定义的值
如果类内包含一个其他类类型的成员且这个成员的类型没有默认构造函数,编译器是不能为类创建合成的默认构造函数

(2)注意以下几点:

构造函数的名字和类名相同,没有返回类型
构造函数不同于其他的成员函数,不能被声明为const的
使用class_name()=default来要求编译器生成默认构造函数(而不管类是否已经有其他构造函数),如果=default在类的内部,则默认构造函数是内联的,如果在类的外部,则该成员函数默认情况下不是内联的

(3)在什么情况下必须使用构造函数初始值列表为成员提供初始值?

成员是const或者是引用
成员属于某种类类型且该类没有定义默认构造函数
除了某些数据成员必须被初始化之外,还有底层效率的问题。使用初始化列表是在进入函数体前构造成员时同时进行初始化,而在函数体中赋值则是先在进入函数体前构造数据成员再在函数体中进行赋值操作。两者的区别主要依赖于数据成员的类型。

注意:成员初始化的顺序与初始化列表的顺序无关,与他们在类中定义的顺序一致。

(4)委托构造函数
举个例子:
class Sale_data{
public:
//非委托构造函数使用对应的是参数初始化成员
Sale_data(std::string s,unsigned cnt,double price):bookNo(s),units_sold(cnt),revenue(cnt*price) {}

//其余构造函数全部委托另一个构造函数
Sale_data():Sale_data("",0,0){}
Sale_data(std::string s):Sale_data(s,0,0){}
Sale_data(std::istream &is):Sale_data()
{ read(is,*this);  }

};

当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体被一起执行。假如函数体包含有代码的话,将先执行这些代码,然后控制权才会交还给委托者的函数体。

(5)隐式的类类型转换
能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。这样隐式的调用也称作转换构造函数
如(4)中定义的接受string和istream的构造函数分别定义了从这两种类型向Sale_data隐式转换的规则。也就是说,在需要Sale_data的地方,我们可以使用string或istream作为替代。

编译器只会自动执行一次类型转换,例如下面的代码隐式地执行了两种类型转换,所以是错误的:
string null_book="999-9-9999";
item.combine(null_book);//使用null_book创建一个Sale_data对象,然后将临时量传递给combine成员函数

item.combine("999-9-9999");//使用了两种转换规则,错误:将“999-9-9999”转换为string,再将string转换为Sale_data对象

//正确的做法是如上第一条或者如下:
item.combine(string("999-9-9999"));//显式转换为string,隐式转换为Sale_data
item.combine(Sale_data("999-9-9999"));//隐式转为string,显式转为Sale_data

若是使用explicit关键字对构造函数的声明进行修饰,就会阻止构造函数定义的隐式转换,将其声明成显式构造函数。
...
explicit Sale_data(const std::string &s):bookNo(s) {}
...

//下面做法就是错误的,因为string对应的构造函数是explicit的
item.combine(null_book);


关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit的。只能在类内声明构造函数时使用explicit关键字,在类外定义时不应该重复。
当我们以explicit关键字声明构造函数时,它将只能以直接初始化的形式(使用=)使用,而且,编译器将不会在自动转换的过程中使用该构造函数。尽管这样,我们可以使用这样的构造函数显式地强制进行转换:
item.combine(Sale_data(null_book));//实参是一个显式构造的Sale_data对象


2、聚合类
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。当一个类满足如下条件时,我们就说它是聚合的:

所有成员都是public的
没有定义任何构造函数
没有类内初始值
没有基类,也没有virtual函数
如:

struct Data{
int val;
string s;
};


我们提供一个花括号括起来的成员初始值列表用来初始化聚合类的数据成员,注意,初始值的顺序必须与声明的顺序一致。 Data vall={"Anna",1024};

3、类的静态成员
(1)声明静态成员
有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。我们通过在成员声明之间加上关键字static使得其与类关联在一起。
静态成员可以是public或private的,静态成员函数不与对象绑定在一起也没有了this指针

类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据,静态成员被所有的类对象共享。

(2)使用类的静态成员

成员函数不用通过作用域运算符就能直接使用静态成员
用户使用作用域运算符直接访问静态成员(包括成员和成员函数): class_name::static_var
用户可以使用类的对象、引用或指针来访问静态成员
(3)定义静态成员

我们可以在类的内部和外部定义静态成员,当在类的外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句。在类的外部定义时要指明所属类的作用域。

一般来说,我们不能在类的内部初始化静态成员,相反的,必须在类的外部定义和初始化每个静态成员,而且静态成员只能定义一次。静态成员和其他成员一样可以访问类的私有成员。一旦它被定义,就将一直存在于程序的整个生命周期中。

4、使用class和struct关键字
实际上我们可以使用这两个关键字中的任何一个定义类,唯一的区别是,struct默认的访问权限是public的,而class默认是private的。

5、友元
如果类想把一个函数或类作为它的友元,只需要增加一条以friend关键字开头的函数声明语句即可。友元可以访问类的非公有成员。
友元的声明只能定义在类的内部,位置不限。但是友元的声明仅仅是指定了访问的权限,而不是一个通常意义上的函数声明。我们必须在友元声明之外再专门对函数进行一次声明以提供函数可见。此外,友元函数可以定义在类的内部,这样的函数是隐式内联的。
为了使友元对类的用户可见,我们通常把友元的单独声明与类本身放置在同一个头文件内。

注意:每个类负责控制自己的友元函数或友元类,友元关系不具有传递性。
如果一个类想把一组重载函数声明成为它的友元,它需要对这组函数中的每一个单独声明,否则只能是对应版本的重载函数成为它的友元。

6、常量成员函数
在成员函数声明的后面加上const,意味着这个成员函数不能修改对象的普通(既不是static也不是mutable)数据成员。const成员的this指针是指向常量的指针,通过区分函数是否是const可以进行重载。
如果在数据成员声明前加上mutable关键字,则该数据成员成为可变数据成员。一个可变数据成员永远不会是const,即使它是const对象的成员。但是一个const成员函数可以改变一个可变成员的值。

7、不完全类型
已经声明但是尚未定义的类型不完全类型不能用于定义变量或者类的成员,但是用不完全类型定义指针或引用是合法的,也可以声明(但是不能定义)以不完全类型作为参数或者返回类型的参数。
比如我们可以仅仅先声明一个类而不定义它,这种声明是前向声明,在它声明后定义前是一个不完全类型。

对于一个类来说,我们必须首先完成类的定义,编译器才能知道存储该数据成员需要多少内存空间。因为只要当类全部完成后才算是被定义,所以一个类的成员类型不能是该类自己。然而,一旦一个类的名字出现后,它就被认为声明过了(但是尚未定义),因此类允许包含指向它自身类型的引用或指针或作为参数或返回类型的参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: