c++ primer复习(五):类

时间:2023-03-09 16:34:21
c++ primer复习(五):类

一:基本内容

1 类

数据成员:用于存储与类对象相关联的状态

成员函数:对数据成员进行操作

类将接口与实现分离,接口指定了类支持的操作,操作的具体实现细节是类的设计者才需要了解

2 类成员

类成员可以是数据(数据成员)、函数(成员函数)、类型别名

数据成员:类不能具有自身类型的数据成员,但是可以使指向自身类型的指针或引用

成员函数:在类内部定义的成员函数默认为内联inline函数,成员函数的内联可以在声明处也可以在定义处指出

     const成员函数不能改变其所操作的对象的数据成员,const成员函数的const必须同时出现在声明和定义中

3 数据抽象和封装

数据抽象依赖于接口与实现相分离的编程技术,使用者只需抽象的考虑对象执行的类操作,无需关心如何实现

类是一个封装实体,类类型隐藏了类的多数成员

访问标号来定义类的抽象接口和实施封装

4 数据成员重载

struct A
{
int a;
}; struct B : A
{
int a;
}; struct B x;
cout << &x << endl;
cout <<&x.a << endl; struct A *pa = &x;
struct B *pb = &x;
cout << &(pa->a) << endl;
cout << &(pb->a) << endl;

结果:

0x7fffc2c00b40  x对象的起始地址

0x7fffc2c00b44  x对象中x.a的地址,可见前面有一个A::a

0x7fffc2c00b40  说明静态绑定,pa的静态类型是A,所以pa->a是A::a

0x7fffc2c00b44  说明静态绑定,pb的静态类型是B,所以pb->a是B::a

普通的数据成员重载,会在派生类中形成两个同名的成员(其实没有重载,而是正常继承了基类的成员,同时正常产生自己的成员,只不过两个成员的名字相同而已)

5 成员函数重载

通过在派生类中重新定义基类函数,派生类可以覆盖、替换掉基类的函数定义

这种覆盖是动态还是静态,取决于成员函数是否被声明为虚函数

class A
{
public:
int a;
virtual void func(){cout << "a ";cout << a << endl;}
}; class B : public A
{
public:
int a;
void func()
{
cout << a << endl;
A::func();
}
}; int main()
{
B b;
b.a = ;
b.A::a = ;
b.func();
b.A::func();
}

执行结果:

2

a 3

a 3

子类中的数据成员无论是否与父类中的数据成员同名,父类的数据都会被正常继承

如果子类重载了父类的成员函数,则会发生覆盖,但是仍然可以通过 父类名::成员函数名 的方式在子类中调用父类成员函数,同时由于数据成员会正常继承下来,所以用子类对象调用父类成员函数时,子类对象中的数据包含了父类的数据成员,成员函数中访问的变量会使用子类中继承下来的变量,无论变量名是否与子类中的相同 

除了虚函数,其他函数和数据都是静态绑定!!

b.A::func()中的func函数含有一个隐含的this指针 A *const,所以this->a将发生静态绑定,将调用实例b中的基类部分的数据a

基于const的重载:可以理解为const成员函数的隐藏this指针类型为 const A *const,非const成员函数的隐藏this指针类型为A *const,形参不同,实现函数重载

6 可变数据成员

即使在const成员函数中,仍然可以被改变的数据成员,需要被声明为mutable成员

二:构造函数

1 初始化列表

构造函数的初始化列表以冒号开始,就算没有显示执行初始化列表,在构造函数函数体执行之前,还是会以默认的方式初始化成员(按照变量声明的顺序)

所以初始化列表可以理解为并不是执行初始化,而是为数据成员初始化提供了一种选择。所以没有默认构造函数的成员一定要在初始化列表中

默认初始化中,内置类型的成员的初始值取决于类对象的作用域,局部作用域中不被初始化,全局作用域中被初始化为0

成员初始化的顺序按照定义的顺序,而不是按照初始化列表中的顺序,这点可能造成程序出错:

class X{
int i;
int j;
public:
X(int val):j(val),i(j){}
};

先初始化i,但初始化列表中i依赖于j,所以i最终初始化的值可能并非我们想要的值(这里如果X对象实例是局部变量,i的值将随机,X若是全局变量,i的值为0)

初始化列表中,可以向数据成员传递满足数据成员类型的构造函数的各种形式的形参

2 默认构造函数

一个类如果定义了自己的构造函数,编译器将不会合成默认的构造函数

需要注意的是,如果一个类A的数据成员中有另一个没有默认构造函数的类B,那么编译器不会为A合成默认构造函数

具有默认构造函数的类A的对象,不能用 A a()进行定义,而是 A a

3 每个构造函数都定义了一个隐式转换

explicit声明的构造函数,可以抑制由构造函数定义的隐式转换,explicit只用于类构造函数的声明中,类外部定义的构造函数,不需要也不能加上explicit限定

三 其他

1 友元函数

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

友元声明只能出现在类定义的内部,通常将友元成组的放在类定义的开始或结尾是个好主意

2 static数据成员

对特定类类型的全体对象而言,有时候需要访问一个全局对象,但是全局对象会破坏封装(一般的用户代码就可以修改这个全局对象),类的static数据成员独立于该类的任意对象而存在,该数据成员与类相关联,而不是与对象相关联

static数据成员必须在全局范围内进行初始化 使用 type classname::x的形式,此时不能指定static限定

static const成员和const static成员,只有int类型可以在类内部初始化,其他任何类型都要在外部全局范围内进行初始化,不能在初始化列表上

static const int既可以在类内部初始化,又可以在外部全局进行初始化,但只能初始化一次

static成员既可以用A::来调用,又可以用a.来调用

无论是public还是private,static数据正常遵循上面的初始化规则,同时正常遵循public、private的权限设定

3 static成员函数

static成员函数没有this指针,可以直接访问所属类的static成员,但是不能访问类的非static成员

static成员函数不能被声明为const(const函数说明函数不修改调用对象的数据,static成员本身就不会访问对象的数据)

static成员函数不能被声明为虚函数