C++(7):类

类的基本思想是数据抽象和封装。
数据抽象 是一种依赖于接口和实现分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
封装 实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节,也就是说,类的用户只能使用接口而无法访问实现部分。

定义抽象数据类型

定义在类内部的函数是隐式的inline函数。

所有成员都必须在类的内部声明,但是成员函数体可以定义在类内也可以定义在类外。

成员函数通过一个名为 this 的额外的隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化 this。this是一个常量指针。

常量对象,以及常量对象的引用或指针都只能调用常量成员函数。

构造函数:初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。

构造函数的名字和类名相同。构造函数没有返回类型;如果没有显式定义构造函数,编译器会自动隐式定义一个默认构造函数。

访问控制与封装

使用访问说明符加强类的封装性:
1.定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
2.定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。

使用class和struct定义类唯一的区别就是默认的访问权限:希望所有成员是 public,用struct;希望成员是 private,用 class。

友元

类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元(friend)。

友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果希望类的用户能够调用某个友元函数,那么就必须在友元声明之外再专门对函数进行一次声明。

类的其他特性

特性包括:类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回*this、关于如何定义并使用类类型及友元类等

类型成员
除了定义数据和函数成员之外,类还可以自定义某种类型在类中的别名。

typedef string::size_type pos;
using pos = string::size_type;

由类定义的类型名字和其他成员一样存在访问权限,可以是 public 或 private。

可变数据成员 :const 成员函数不能修改成员变量。用 mutable 将成员修饰为可变数据成员,就可以变得可修改。

当我们提供一个类内初始值时,必须以符号 = 或者花括号表示。

返回*this的成员函数
返回引用的函数是左值的,意味着这些函数返回的是对象本身而非对象的副本。
this 指针指向类本身,即 this 是类的地址,*this 就是类本身。

一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。

类类型 :每个类定义了唯一的类型。对于两个类来说,即使它们的成员完全一样,这两个类也是两个不同的类型。

友元友元关系不存在传递性。每个类负责控制自己的友元类或友元函数。
可以把其他的类定义成友元,也可以把其他类的成员函数定义成友元。如果一个类指定了友元类。则友元类的成员函数可以访问此类的所有成员。
尽管重载函数的名字相同,但它们仍然是不同的函数。因此,如果一个类想把一组重载函数声明成它的友元,它需要对这组函数中的每一个分别声明。

类的作用域

每个类都会有自己的作用域。
在类的作用域之外,普通的数据和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。
对于类类型成员则使用作用域运算符来访问。

类的定义:编译器处理完类中的全部声明后才会处理成员函数的定义。

内层作用域可以重新定义外层作用域中的名字。而在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。

类型名的定义通常出现在类的开始处,这样就能确保所有使用该类型的成员都出现在类名的定义之后。

构造函数

构造函数初始值列表

当成员属于某种类类型 且该类没有定义默认构造函数时,必须将这个成员初始化。

class ConstRef {
public:
	ConstRef (int ii);
private:
	int i;
	const int ci;
	int &ri ;
};

//错误:ci和ri必须被初始化
constRef :: ConstRef (int ii){
	//赋值:
	i= ii;//正确
	ci = ii;//错误:不能给const赋值
	ri = i;//错误:ri没被初始化
}

如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,就必须通过构造函数初始值列表为这些成员提供初值。

在很多类中,初始化和赋值的区别事关底层效率问题:前者直接初始化数据成员,后者则先初始化再赋值。

构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序。

如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。

委托构造函数

委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程。

class Sales_data{
public:
	//非委托构造函数使用对应的实参初始化成员
	Sales_data(std::string s,unsigned cnt,double price):
		bookNo(s), units_sold(cnt), revenue (cnt*price){ }
	//其余构造函数全都委托给另一个构造函数
	Sales_data(): Sales_data(" ", 0,0) {}
	Sales_data(std::string s): Sales_data(s,0,0){}
	Sales_data(std::istream &is): Sales_data()
		{read(is,*this);}
	//其他成员与之前的版本一致
};

默认构造函数的作用

在实际中,如果定义了其他构造函数,最好也提供一个默认构造函数。

隐式的类类型转换

如果构造函数只接受一个实参,它实际上定义了转换为此类类型的隐式转换机制,有时会把这种构造函数称为转换构造函数

编译器只允许一步类类型转换。

//错误:需要用户定义的两种转换:
//(1)把“9-999-99999-9”转换成string
//(2)再把这个(临时的)string转换成Sales_data
item. combine( "9-999-99999-9");

//正确:显式地转换成string,隐式地转换成Sales_data
item.combine(string("9-999-99999-9"));
//正确:隐式地转换成string,显式地转换成Sales_data
item.combine(Sales_data("9-999-99999-9").;

可以通过将构造函数声明为 explicit 加以阻止其隐式转换。只能在类内构造函数声明使用。
关键字 explicit 只对一个实参的构造函数有效。需要多个实参的构造函数不能执行隐式转换。

explicit 构造函数只能用于直接初始化。

Sales_data item1(null_book);//正确:直接初始化
//错误:不能将explicit构造函数用于拷贝形式的初始化过程
Sales_data item2 = null_book ;

尽管编译器不会将 explicit 的构造函数用于隐式转换过程,但是可以使用这样的构造函数显式地强制进行转换:

//正确:实参是一个显式构造的Sales_data对象
item.combine(Sales_data (null_book)) ;
//正确:static_cast可以使用explicit的构造函数
item.combine(static_cast<Sales_data>(cin)) ;

标准库中含有显式构造函数的类:
1.接受一个单参数的 const char* 的 string 构造函数不是 explicit 的。
2.接受一个容量参数的 vector 构造函数是 explicit 的。

聚合类

聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。满足以下条件:
1.所有成员都是 public 的。
2.没有定义任何构造函数。
3.没有类内初始值。
4.没有基类,也没有 virtual 函数。

struct Data{
	int ival;
	string s;
};

// 初始化聚合类:val1 .ival =0; val1.s = string ( " Anna")
Data val1 = { 0"Anna" };

字面值常量类

constexpr 函数的参数和返回值必须是字面值类型。
算数类型、引用、指针以及一些类都是字面值类型。
字面值类型的类可能含有 constexpr 函数成员,这样的成员必须符合 constexpr 函数的所有要求,它们是隐式 const 的。

数据成员都是字面值类型的聚合类是字面值常量类。
如果不是聚合类,满足以下四个条件的类也是字面值常量类:
1.数据成员都是字面值类型。
2.类至少含有一个 constexpr 构造函数
3.如果一个数据成员有类内初始值,则初始值必须是常量表达式;如果成员是某种类类型,则初始值必须使用成员自己的 constexpr 构造函数。
4.类必须使用析构函数的默认定义。

构造函数不能是 const 的,但字面值常量类的构造函数可以是 constexpr 函数。
constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式。
constexpr 构造函数的函数体应该是空的(constexpr 既要符合构造函数的要求,又要符合 constexpr 函数的要求:constexpr 函数的函数体只能包含一条返回语句,而构造函数不能包含返回语句)

类的静态成员

类的静态成员与类本身直接关联,而不是与类的对象保持关联。
静态成员可以是 public 和 private 的。静态数据成员的类型可以是常量、引用、指针、类类型等。

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

静态成员函数也不与任何对象绑定在一起,不包含 this 指针,不能声明为 const 的,不能在 static 函数体内使用 this 指针。

static关键字只出现在类内部的声明语句中。

只有 constexpr 类型的静态数据成员可以在类内初始化,但是也需要在类外定义。

重要术语

默认构造函数(default constructor) 当没有提供任何实参时使用的构造函数。
委托构造函数(delegating constructor) 委托构造函数的初始值列表只有一个入口,指定类的另一个构造函数执行初始化操作。
转换构造函数(converting constructor) 可以用一个实参调用的非显式构造函数。这样的函数隐式地将参数类型转换成类类型。
显式构造函数( explicit constructor) 可以用一个单独的实参调用但是不能用于隐式转换的构造函数。通过在构造函数的声明之前加上explicit关键字就可以将其声明成显式构造函数。
合成默认构造函数(synthesized defaultconstructor) 对于没有显式地定义任何构造函数的类,编译器为其创建(合成)的默认构造函数。该构造函数检查类的数据成员,如果提供了类内初始值,就用它执行初始化操作;否则就对数据成员执行默认初始化。

= default 一种语法形式,位于类内部默认构造函数声明语句的参数列表之后,要求编译器生成构造函数,而不管类是否已经有了其他构造函数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞大圣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值