C++ Primer(十二) 类

12.1 类的定义和声明


12.1.1 类定义:扼要重述

一旦类定义完成后,就没有任何方式可以增加成员了。

2. 构造函数

创建一个类类型的对象时,编译器会自动使用一个构造函数来初始化该对象。

构造函数初始化列表由成员名和带括号的初始值组成,跟在构造函数形参表之后,并以冒号开头。

3. 成员函数

在类内部定义的成员函数默认为inline。

成员函数有一个附加的隐含实参,将函数绑定到调用函数的对象。

将const加在形参表之后,就可以将成员函数声明为常量:

double avg_price() const;

const成员不能改变其所操作对象的数据成员。const必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。

 

改变头文件中的类定义可以有效改变包含该头文件的每个源文件的程序文本,所以,当类发生改变时,使用该类的代码必须重新编译。

12.1.3 关于类定义的更多内容

3. 成员函数可以被重载

两个重载成员的形参类型和数量不能完全相同。

12.1.4 类声明与类定义

将类定义放在头文件中,可以保证每个使用类的文件中以同样方式定义类。

可以声明一个类而不定义它

class Screen;

这个声明,有时称为前向声明

在声明之后,定义之前,该类Screen是一个不完全类型。

不完全类型只能以有限方式使用。不能定义该类型的对象。不完全类型只能定义指向该类型的指针或引用,或者用于声明使用该类型作为形参或者返回类型的函数

在创建类对象前,必须完整地定义类。必须定义类而不是声明类,这样,编译器就会给类的对象预定相应的存储空间。 在使用引用或者指针访问类的成员之前,必须已经定义类。

 
为类的成员使用类声明

只有当类定义在前面出现过,数据成员才能指定为该类类型。如果该类型是不完全类型,那么数据成员只能是指向该类类型的指针或者引用。

因为只有当类定义体完成后才能定义该类,因此类中不能拥有自身类型的数据成员。

类的数据成员可以是指向自身类型的指针或者引用。

12.1.5 类对象

定义类是定义了一个新类型,但没有进行存储分配。

但我们定义Sales_item item;

时,编译器分配了一个Sales_item对象的存储空间。每个对象有自己的类数据成员的副本。

12.2 隐含的this指针

成员函数有一个隐含的形参,即指向类对象的指针,这个隐含的指针命名为this,与调用成员函数的对象绑定在一起。成员函数不能定义this形参

3.从const成员函数返回*this

在普通的非const成员函数中,this的类型是一个指向类类型的const指针。可以改变this所指向的值,不能改变this保存的地址。在const成员函数中,this的类型是指向const类类型对象的const指针,既不能改变this指向的对象,也不能改变this保存的地址。

const成员函数*this只能作为一个const引用返回。

const对象只能调用const成员函数

4.基于const的重载

基于成员函数是否为const可以重载一个成员函数。const对象只能调用const成员,非const对象可以使用任一成员。

5. 可变数据成员

可以讲数据成员声明为mutable:可变数据成员。

可变数据成员永远不能为const,甚至它是const对象的成员时。cosnt成员函数可以改变mutable成员。

12.3 类作用域

在类的定义体内声明类成员,将成员名引入类的作用域。

即使两个类具有完全相同的成员列表,它们也是不同的类型。

 

3. 形参表和函数体处于类作用域

在定义于类外部的成员函数中,形参表和成员函数体出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员。

如果返回类型使用由类定义的类型,则必须使用完全限定名。

类作用域中的名字查找

名字查找:
(1)首先,在使用改名字的块中查找名字声明。只考虑在该项使用之前声明的名字。

(2)如果找不到改名字,则在包围的作用域中查找。

如果找不到任何声明,则程序出错。所有名字必须在使用前声明。

类定义实际上是在两个阶段中处理:

(1)首先,编译成员声明;

(2)只有在所有成员出现后,才编译它们的定义本身

1.类成员声明的名字查找

按一下方式确定在类成员的声明中用到的名字:
· 检查出现在名字使用之前的类成员的声明。

· 如果第一步查找不成功,则检查包含类定义的作用域中出现的声明以及出现在类定义之前的声明。

2. 类成员定义中的名字查找

以下方式确定在成员函数体中用到的名字:

·首先检查成员函数局部作用域中的声明。

·如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。

·如果在类中找不到该名字的声明,则检查此成员函数定义之前的作用域中出现的声明。

尽管全局作用域被类内部名字屏蔽了,还是通过全局作用域确定操作符来限定名字。

::height

当成员函数定义在类定义的外部时,不仅要考虑在类定义之前的全局作用域中的声明,而且还有考虑在成员函数定义之前出现的全局作用域声明。

12.4 构造函数

只要创建类类型的新对象,就要执行构造函数。

  1: class Sales_item{
  2: public:
  3: Sales_item():units_sold(0),revenue(0.0){}
  4: private:
  5: string isbn;
  6: unsigned units_sold;
  7: double revenue;
  8: }

这个构造函数使用构造函数初始化列表来初始化类数据成员。isbn成员由string的默认构造函数隐式初始化为空串。

4. 用于const对象的构造函数

构造函数不能声明为const

class Sales_item{

public:

Sales_item()const;//错误

错误

}

创建类类型的const对象时,运行一个普通的构造函数来初始化const对象。

12.4.1构造函数初始化式

构造函数含有一个构造函数初始化列表

构造函数初始化列表只能在构造函数的定义中而不是声明中指定。

构造函数可以分为两个阶段执行:(1)初始化阶段(2)普通的计算阶段,计算阶段由构造函数的函数体中的所有语句组成。

不管成员是否在构造函数初始化列表中显示初始化,类类型的数据成员总是在初始化阶段初始化,初始化发生在计算阶段开始之前。

在构造函数初始化列表没有显示提及的每个成员,如果是类类型成员,运行该类的默认构造函数,来初始化该类类型的数据成员。

内置类型或复合类型的成员的初值依赖于对象的作用域;在局部作用域中定义的对象中这些成员不初始化,而在全局作用与中它们被初始化为0.

1.有时需要构造函数初始化列表

如果没有为类成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数,如果该类型没有默认构造函数,则编译器尝试使用默认构造函数会失败。

有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在函数体中对它们赋值不起作用,成员如下:

没有默认构造函数的类类型的成员、const或引用类型成员

2. 成员初始化的次序

每个成员在构造函数初始化列表中只能指定一次。成员被初始化的次序就是声明成员的次序。

3.初始化式可以是任意表达式

12.4.2 默认实参与构造函数

接受一个string和接受一个istream&的构造函数都具有默认实参时不合法的,如果二者都具有默认实参,则重复定义默认构造函数,当定义对象而没有指定实参时,将无法确定使用哪个默认构造函数而出现编译错误


12.4.3 默认构造函数

1.合成的默认构造函数

一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。

只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。

合成的默认构造函数

具有类类型的成员,使用该类的默认构造函数来进行初始化。内置和复合类型的成员,只对定义在全局作用域中的对象才初始化,当对象定义在局部对象时,内置类型或复合类型不进行初始化。

2.类通常定义一个默认构造函数

默认构造函数是由编译器隐式调用的。

假定有一个Nodefault类,它没有定义自己的默认构造函数,却又一个接受string实参的构造函数。因为该类定义了一个构造函数,因此编译器不合成默认构造函数,Nodefault没有默认构造函数,意味着:

(1)具有Nodefault成员的每一个类的每个构造函数,必须通过传第一个string值给Nodefault构造函数来显示初始化Nodefault成员。

(2)编译器不会为具有Nodefault类型成员的类合成默认构造函数。

(3)Nodefault类型不能用作动态分配数组的元素类型

(4)Nodefault类型的静态分配数组必须为每个元素提供一个显式的初始化式

(5)如果有一个保存Nodefault对象的容器,例如vector,就不能使用接受容器大小而没有提供一个元素初始化式的构造函数。

12.4.4 隐式类类型转换

可以用单个实参调用的构造函数定义了从形参类型到该类类型的一个隐式转换。

1. 抑制由构造函数定义的隐式转换

可以通过将构造函数声明为explicit,来防止需要隐式转换的上下文中使用构造函数

explicit关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它。

12.5友元

允许特定的非成员函数访问一个类的私有成员。

友元机制允许一个类将对其非公有成员的访问权限授予指定的函数或者类。

必须先定义包含成员函数的类,才能将成员函数设为友元。

友元声明将已将命名的类或非成员函数引入到外围作用域中。此外,友元函数可以在类内部定义,该函数的作用域扩展到包含该类定义的作用域。

4.重载函数与友元的关系

类必须将重载函数集中的每一个希望设为友元的函数声明为友元。

12.6 static类成员

类可以定义类静态成员。

非static数据成员存在于每一个类类型的对象中。static数据成员独立于该类的任意对象而存在;每个static数据成员是与类关联的对象,而不与该类的对象关联。

static成员函数没有this形参,它可以直接访问所属类的static数据成员,但不能直接使用非static成员。

类成员函数可以不使用作用域操作符来饮用类的static成员。

12.6.1 static成员函数

当在类的外部定义static成员时,无需重复指定static保留字,该保留字只在类定义体内部的声明处

static函数没有this指针

static成员是类的组成部分但不是任何对象的组成部分,因此static成员函数没有this指针。

因为static成员不是任何对象的组成部分,所以static成员函数不能声明为const。static成员函数也不能声明为虚函数

12.6.2 static数据成员

static数据成员必须在类定义体的外部定义。static数据成员是不通过类构造函数进行初始化,而是在定义时初始化。

1. 特殊的整型const static成员

只要初始化式是一个常量表达式,整型const static数据成员就可以在类的定义体中进行初始化

const static数据成员在类的定义体内进行初始化时,该数据仍然必须在类的定义体外进行定义。

2. static数据成员不是类对象的组成部分

普通成员都是给定类的每个对象的组成部分。static成员独立于任何对象而存在,不是类类型对象的组成部分。

static数据成员可以是该成员所属的类类型。

class bar{

public:

private:

static bar mem1;

bar *m2;

};

类似地,static数据成员可用作默认实参:

class Screen{

public:

Screen &clear(char=bkground);

private:

static const char bkground=‘*’;

 

};

非static数据成员不能用作默认实参,因为它的值不能独立于所属对象而是用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值