c++ primer 面向对象编程笔记

本文详细介绍了C++中的动态绑定、虚函数、protected成员等概念,并探讨了作用域、派生类到基类的转换规则及构造函数的相关注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1) 动态绑定

C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象。

(2) 虚函数

1) 保留字virtual只在类内部的成员函数声明中出现,不能用在类定义体外部初相的函数定义上。

2) 派生类中虚函数的声明必须与基类中的定义完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数类型的派生类的引用(或指针)

1
2
3
4
5
6
7
8

classBase
{
virtualBase&f();
};
classDerive:publicBase
{
Derive&f();
};

3) 默认实参

1、 通过基类的引用或指针调用虚函数时,默认实参是基类声明中的值;

2、 通过派生类的指针或引用调用虚函数时,则默认实参是派生类声明中的值;

这里就会产生一种情况,通过基类的引用或指针调用,而实际上执行的是派生类中定义的版本,而默认实参确实基类定义的参数

01
02
03
04
05
06
07
08
09
10
11
12

classBase
{
public:
virtualvoidf(intx=1);
};
classDerive:publicBase
{
public:
voidf(intx=2);
};
Base*pBase=newDerive;
pBase->f();//
默认实参x=1,调用Derive::f()

(3) protected成员

1) private成员一样,protected成员不能被类的用户访问;

2) public成员一样,protected成员可被该类的派生类访问;

01
02
03
04
05
06
07
08
09
10
11
12
13

classBase
{
protected:
intx;
};
classDerive:publicBase
{
voidf(Derive&d,Base&b)
{
d.x=1;
//b.x=1;//error
}
};

(4) 作用域

在继承情况下,派生类的作用域嵌套在基类作用域中。在基类和派生类中使用同一名字的成员函数,在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同

01
02
03
04
05
06
07
08
09
10
11
12

classBase
{
public:
voidf();
};
classDerive:publicBase
{
public:
voidf(intx);
};
Derive*d;
//d->f();//error

解决的方法有两种,第一种是显示使用作用域操作符,第二种使用using声明获得重载函数的行为。一个using声明只能指定一个名字,不能指定形参表。有一种特殊情况,如果using声明的函数原型在派生类中已有定义,则派生类中的函数依然会屏蔽基类中的函数。

01
02
03
04
05
06
07
08
09
10
11
12

//方法一:显示使用作用域
d->Base::f();

//
方法二:使用using声明
classDerive:publicBase
{
public:
usingBase::f;
//...
};
Derive*d=newDerive;
d->f();

using声明的另一种作用是改变继承成员的访问级别,但不能使访问级别比基类中原来指定的更宽松。

01
02
03
04
05
06
07
08
09
10

classBase
{
public:
voidf();
};
classDerive:privateBase
{
public:
usingBase::f;//
这里的f函数恢复为public
};

对于虚函数,有两个原则:1、只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制;2、派生类虚函数调用基类版本时,只能显式使用作用域操作符。

(5) 派生类到基类转化的可访问性

要确定到基类的转换是否可访问,可以考虑基类的public成员是否可访问,如果可以,转换是可访问的,否则转换是不可访问的。

这里可以这样理解,如果在派生类中不能访问基类的public成员,则说明派生类中不能访问基类的任何成员(派生类中对基类那部分完全不可见且不可操作),所以在该派生类中任何出现基类类型的成员函数(输入形参或返回类型)、成员变量都是错误,这里包括了从该派生类到基类的转换函数,所以不存在这样的转化函数(无论是默认或自己定义),也即不可访问的。

01
02
03
04
05
06
07
08
09
10
11

classA{};
classAA:privateA{};
classAAA:publicAA
{
::Am;//ok
//Am;//error
//voidf(constA&);//error
};

AAAaaa;
//Aa=aaa;//error

(6) 构造函数和复制控制

1) 派生类构造函数不能初始化基类的成员且不应该对基类成员赋值,这样做会违反基类的接口,派生类应通过使用基类构造函数尊重基类的初始化意图。

2) 如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分

3) 如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分显式赋值

4) 即使析构函数没有工作做,继承层次的根类也应该定义一个虚析构函数。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19

classA
{
public:
A&operator=(constA&m);
virtual~A(){}//
虚析构函数
};
classAA:publicA
{
public:
AA(AA&m):A(m)//
复制构造
{
//...
}
AA&operator=(constAA&m)//
赋值操作符
{
A::operator=(m);
//
或者直接对基类成员赋值
}
};

(7) 部分知识点

1) 如果需要声明一个派生类,则声明包含类名但不包含派生类列表。

2) 使用class保留字定义的派生类默认具有private继承,用struct保留字定义的类默认具有public继承。

3) 基类的友元对从该基类派生的类型没有特殊访问权限

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值