C++ primer学习笔记 第七章

第七章 类

我们使用类来定义自己的数据类型。类的基本思想是数据抽象和封装,数据抽象是一种依赖于接口和实现分离的编程计算,要想实现类的数据抽象和封装,首先需要定义一个抽象数据类型,类的设计者负责考虑类的实现过程,使用类的程序员只需要思考类做了什么。

数据抽象———定义数据成员和函数成员的能力;

封装———保护类的成员不被随意访问的能力,通通过将类的实现细节设为privata。

1.定义抽象数据类型

成员函数的声明必须在类的内部,定义可以在类内也可以在类外(需要加上作用域),作为接口组成部分的非成员函数,它们的定义都在类的外部。

1.this

我们调用成员函数时,实际上是替某个对象调用它,成员函数通过名为this的额外的隐式参数来访问调用它的那个对象。例如total.isbn()调用isbn成员函数,则编译器会将total的地址传递给isbn的隐式形参this

一个成员调用另一个成员的时候,this指针在其中隐式地传递。

在成员函数内部,我们可以直接使用调用该函数的对象的成员而无需成员访问运算符,因为this指向的就是这个对象。

**this是一个常量指针,**不允许改变this中保存的地址。

2.const成员函数

std::string isbn() const {return bookno;}

const是修改隐式this指针的类型,默认情况this是指向类类型非常量版本的常量指针,因此不能指向常量对象。又因为this是隐式的,没地方声明,所以c++允许把const放在成员函数参数列表之后,表示this是一个指向常量的指针,这样的函数称为常量成员函数

3.定义类相关的非成员函数

如果非成员函数是类接口的组成部分,那应该和类声明在同一个头文件内。

类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

4.构造函数

构造函数的任务是初始化类对象的数据成员,只要类的对象被创建,就会执行构造函数。

构造函数的名字和类名相同。没有返回类型,类可以包含多个构造函数(重载函数)。构造函数不能被声明成const类型,当我们创建类的一个const对象时,知道构造函数完成初始化的过程,对象才能真正去的常量属性,因此构造函数在const对象的构造过程中可以写值。但是字面值常量类的构造函数可以是constexpr类型。

合成的默认构造函数:当类没有声明任何构造函数时,编译器创建的构造函数,为对象提供默认初始化。之前说过,定义在块中的内置类型或复合类型对象默认初始化值时值是未定义的,除非提供了初始值,因此可能产生错误。

默认构造函数:除了很简单的类适合合成的默认构造函数,普通的类需要定义默认构造函数控制默认初始化的过程,无需任何实参

构造函数最好使用类内初始值+默认构造函数sale_data()=default;

如果编译器不支持类内初始值,使用构造函数来赋值。sale_data (const string&s):book(s) { }

5.拷贝,赋值和析构

除了定义类的对象如何被初始化之外,类还需要控制拷贝,赋值和销毁对象时发生的行为。

如果不主动定义,**编译器会替我们合成他它们。但对于某些类来说合成的版本无法正常工作。**如果类包含vector或者string成员,那么其合成版本能正常工作。

2.访问控制与封装

C++语言中,我们使用访问说明符加强类的封装性。定义在public后的成员可以在整个程序内访问,public成员定义类的接口;定义在private后的成员可以被类的成员函数访问,但不能被使用该类的代码访问,即private部分封装了类的实现细节。

封装:确保用户代码不会无意间破坏封装对象的状态;被封装类的具体实现细节可以随时改变而无需调整用户级别的代码。

class 和 public定义类的唯一区别就是默认的访问权限。

struct——定义在第一个访问说明符之前的成员是public的;

class——定义在类的第一个访问说明符之前的成员是private的。

1.友元

类可以允许其它类或者函数访问它的非公有成员,方法是把其它类或者函数定义为它的友元。即在这个类中加一条friend关键字开始的函数声明语句即可。友元不是类的成员,不受它所在区域访问控制级别的约束。

**友元的声明不是传统意义上函数的声明,**如果要类的用户能够调用某个友元函数则需要在友元声明外再专门对函数进行声明。

3.类的其它特性

1.类成员再探

类可以自定义某种数据类型的别名(typedf或者using)。

类成员可以是内联函数。

成员函数可以发生重载。

变量声明前加上mutable关键字就是可变数据成员,即使再const成员函数内,它的值也可以被改变。

一个类的成员可以是另一个类,提供类内初始值时必须用=或者{}。

2.返回*this类型的成员函数

inline Screen &screen::set(char c)``//返回类型定义为引用,那返回值就是调用screen的对象的引用,意味着返回值返回的是对象本身而不是对象的副本,可以用myscreen.move(4,2).set('m')这种连着的,因为都是在同一个对象上执行

{

contents[cursor] = c;

return *this;

}

const成员以引用的形式*this,那么它的返回类型时常量引用。

3.类类型

两个类即使成员列表完全一致也是不同的类型。

4.友元再探

1.普通的非成员函数定义成友元
2.类之间的友元

在scren类中声明frind class window,那么window类就可以访问screen中的所有成员。

友元不存在传递性。

3.令成员函数作为友元

把一个成员函数声明成友元,必须在函数名前加上作用域。

4.函数重载和友元

重载函数是不同的函数,需要单个声明。

5.友元声明和作用域

注意友元声明只是影响访问权限,并不是普通意义上的声明。如果要使用该函数还是要声明。

4.类的作用域

**一个类就是一个作用域,所以在类外定义成员函数需要同时提供类名和函数名。**由此成员函数的函数列表和函数体就在类的作用域之内了。如果返回类型不是内置类型而是由某个类定义的,那么返回类型也需要提供类名。

void windows::clear(int i);

编译器处理完类中的全部声明后才会处理成员函数的定义,所以成员函数能使用类中的所有名字。

一般内层作用域可以重新定义外层作用域中的名字(typedef),但是类中不能重新定义外层作用域中的名字。

一般不建议使用成员的名字作为某个成员函数的形参列表的名字。如void dummy (int h){cr=h*o;}里面用到的h是参数声明而并不是前面定义的。或者给形参h改个名字,或者可以通过域运算符::加在h*o的h前面访问想要的h。名字的查找顺序是由内向外的,查到一个就不往前查了,太懒了。

5.构造函数再探

1.构造函数初始值列表

初始化和赋值不一样,前者直接初始化数据成员,后者则先初始化再赋值。建议养成使用构造函数初始值的习惯。

特别如果成员是const,引用,或者属于某个未定义默认构造函数的类时必须初始化。

class ConseRef{

public:

ConseRef(int ii);

private:

int i;

const int ci;

int &ri;

}

//错误

ConseRef::ConseRef(int ii){

i=ii; ci=ii; ri=i;

}

//正确

ConseRef::ConseRef(int ii):i(ii),ci(ii),ri(i){};

构造函数初始化执行顺序不限,除非后面的初始化用到了前面的值。

2.委托构造函数

**一个委托构造函数使用它所属类的其它构造函数来执行它自己的初始化过程(这也太会了),**受委托的构造函数的初始值列表和函数体被一依次执行,然后控制权才会交给委托人家的函数体。

3.默认构造函数的作用

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

要声明一个用默认构造初始化的对象:

Sale_data obj(); //错误,声明了一个函数而非对象

Sale_data obj2; //正确,obj2是一个对象而非函数

4.隐式的类类型转换(转换构造函数)

如果构造函数只接受一个实参(需要多个实参的构造函数不能实现隐式类型转换),则它实际上定义了转换为此类类型 的隐式转换机制,有时我们把这种构造函数称为转换构造函数。

string book="999";

item.combine(book);//用一个string实参调用了combine成员,编译器用string自动创建了一个sale_data对象传递给combine。

在构造函数之前加explicit关键字则没有任何函数能隐式创建sale_data对象。只能在类内使用。

5.聚合类

6.字面值常量类

尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr类型。事实上一个字面值常量类必须至少提供一个constexpr构造函数。

6.类的静态成员

类的一些成员与类本身相关,而不是与类的各个对象保持关联。

1.声明静态成员

在成员的声明前加上static关键字就是静态成员。静态成员可以是public或private的,静态数据成员的类型可以是常量,引用,指针,类类型等。

对象中不包含任何与静态数据成员有关的内容。类似的,静态成员函数也不与任何对象绑定在一起,不包含this指针。

2.使用静态成员

成员函数可以直接使用静态成员,非成员函数可以使用域运算符访问静态成员。

虽然静态成员不属于类的某个对象,但我们依然可以用类的对象,引用或指针来访问静态成员。

**Account ac1;**

Account *ac2=&ac1;

r=ac1.rate();

r=ac2->rate();

3.定义静态成员

类内或者类外都可以定义静态成员函数,但是类外定义时不能重复static关键字,static关键字只能出现在类内部的声明语句中。

**静态数据成员不属于类的任何一个对象,**所以它们并不是在创建类的对象时被定义的,意味着它们不是由类的构造函数初始化的。而每个静态成员都需要在类外定义和初始化(数据类型 类名::静态成员=初始化值)。

当静态成员是字面值常量类型的constexpr时。可以在类内初始化,初始值必须是常量表达式。一般此时还应该在类外定义一下(不带初始值)。

static constexpr int p=30;

**静态成员可以作为默认实参,但是非静态成员不能,**因为它的值本身属于对象的一部分,这样做无法真正提供一个对象以便从中获取成员的值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值