Cpp面向对象程序设计

写代码时犯的错误

1.static变量没有初始化,导致编译出现奇怪问题

int Score::totalScore=0;

笔试

常对象

教材内容

如果用常引用做形参,那么可以保证实参不会意外地被修改
常对象在声明的同时必须被初始化
this指针不能指向常对象
常成员函数只能访问对象的数据成员,而不能修改它们的值,也不能在函数体中调用非const修饰的成员函数。C++规定,常对象只能调用类的常成员函数以及类的静态成员函数
重载:常对象调用常成员函数,普通对象调用普通函数
构造函数对该数据成员的初始化也比较特殊,只能通过初始化列表来完成。
const int A::b=10;

一个指向常对象的指针是可以指向一个非const型的对象的,但是指向的对象是不能通过指针来改变的。
一个类型为常对象的指针,如果指向了一个非常对象,那么在通过这个指针调用非常对象时,将其看做常对象来看待(即为了保证该非常对象不被更改)
常引用也是如此,在函数内部也将引用的变量看做const类型
e.g.

#include <iostream>
using namespace std;
class A
{
public:
	virtual void Show() const { cout << "a" << endl; }
	void Show() { cout << "b" << endl; }
	};
};
void Refer(const A &a) { a.Show(); }
int main()
{
	A a;
	const A *p = &a;
	Refer(a);
	p->Show();
	return 0; 
}

输出:
a
a

常成员函数定义:
void show() const;
引申问题:
this?
静态成员函数?

this指针

每个类都拥有一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,用来指向正在调用某个成员函数的对象的地址。这就是this指针。每个类都拥有一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,用来指向正在调用某个成员函数的对象的地址。这就是this指针。
尽管this指针可以被显式地描述出来,但一般情况下都没必要。那么什么时候才必须显式地描述呢?一种情况是,在类的非静态成员函数中返回类对象本身的时候,直接使用
return *this;
另外一种情况是当参数与数据成员同名时使用this指针,如
this->n = n。

静态成员

静态成员函数不能访问普通数据成员,它的作用主要是操纵同类中的静态数据成员。静态成员函数让人感觉有点类似于普通成员函数——都可以被所有对象使用,但实际上两者之间有本质的区别。类的普通成员函数都拥有this指针。当某对象调用成员函数时,系统先将该对象的地址赋给this指针,成员函数在执行时,由this指针标识数据成员所属的对象。而静态成员函数没有this指针,但可以通过类名或对象名来实现对它的访问。如果静态成员函数在执行过程中出现有非静态的数据成员,那么这个数据成员就无法被指明所属对象,无法进行正确的存取,函数也就无法再执行下去。

静态成员和类的非静态数据成员一样存在着访问控制,如果把访问权限设为private,照样可以让静态数据成员隐藏在类内部,外界无法对其进行操作,这样就不会破坏封装的原则,保证了数据的安全性。
静态数据成员也存在使用前的初始化问题,但它的初始化不能在构造函数中进行。原因是,静态数据成员不专属于任何一个对象,而构造函数是在创建对象时调用的函数,它只负责初始化专属于该对象的数据成员。
初始化静态数据成员是在类声明之外进行的,其一般语法格式如下:
数据类型 类名 :: 静态数据成员名 = 初始值 ;
由于只属于类所有,静态数据成员的访问形式有别于普通的数据成员。它既可以通过对象名+句点符号的形式来访问,而更多的时候则是通过以下的语法形式来访问:
类名 :: 静态数据成员名;

多态性

静态多态性-编译时多态性:通过函数重载实现
动态多态性-运行时多态性:通过虚函数与继承来实现

运算符重载

1.运算对象中至少含有一个用户自定义的数据类型。这主要是限制用户为标准数据类型重载运算符。
2.不能违背运算符原来的语法规则
3.不能改变运算符原来的优先级和结合性等性质。
4.只能重载C++已经预定义的运算符,不能自创运算符进行重载
5.可以改变操作数类型,不能改变操作数个数

纯虚常函数

virtual void show() const=0

抽象基类

不能直接使用抽象基类创建对象,但可以通过抽象基类的指针或者引用访问其派生类的虚函数。纯虚函数和抽象类的好处是提供了一个必须由派生类自己去实现的公共界面,从而使的类层次结构更加清晰。
当一个类中至少含有一个纯虚函数时,这个类就变成了抽象类(abstract class)。抽象类中必须至少含有一个纯虚函数。抽象类中的纯虚函数也可从其父类中继承而来。也就是说派生类也有可能依然是抽象类。即含有纯虚函数的抽象类只能用来派生新类,而如果其派生类未实现基类的全部纯虚函数,该派生类仍将被看作一个抽象类。

虚函数

调用基类中的同名虚函数:
A::show()
(A为基类,show为同名函数)

静态联编和动态联编

对于调用普通成员函数来说,都是静态联编,这个没问题。但是说对于虚函数来说,如果是通过对象调用虚函数,不会经过查虚函数表,是静态联编;如果是通过指针调用虚函数,就会经过查虚函数表,是动态联编。

通过指针和对象调用成员函数的区别(无虚函数)

对象:
如果使用对象调用,则不存在不能正确调用Show( )函数的问题。但仅用对象调用也有很大的局限性,至少无法实现动态多态性。

指针,引用:
无论基类指针poCS指向基类对象oCS还是派生类对象oCC,系统都只调用了基类CShape的Show( )函数,没有调用派生类CCricle自己定义的Show( )函数。
引用同理

虚函数

虚函数的作用就是实现动态联编,即在程序运行时才绑定即将调用的函数体。但是必须通过基类的指针或者引用才能实现动态联编(通过基类的对象调用虚函数只能实现静态联编)。如果通过基类的指针调用虚函数,则有以下两种结果:当该指针指向基类对象时,则调用的是基类中定义的虚函数;当该指针指向派生类对象时,则调用的是派生类中定义的虚函数。

C++的虚函数定义需要遵循以下几条重要规则:
(1)、在基类和派生类中定义的同名虚函数如果其形式参数或者返回值类型有任何不同,那么即便都加上了关键字virtual,也不会进行滞后联编,不具有虚特性。
(2)、虚函数必须是成员函数,不能是类外的普通函数,因为虚函数仅适用于有继承关系的类对象。
(3)、虚函数不能是静态成员函数,因为静态成员函数不属于对象而属于类,它都是早期联编的。
(4)、虚函数不能是内联( inline )函数,因为内联函数在编译时采用插入函数体的方式进行处理,所以它必须进行早期联编。因此,虚函数即使在类的内部进行定义,但在编译时系统仍然将它看做是非内联的。
(5)、构造函数不能是虚函数,因为构造对象时,对象还是一片未定型的空间,只有对象构造完成后,对象才是具体类的实例。
(6)、析构函数可以是虚函数,尤其在类具有指针成员或者具有动态内存分配运算符时,通常需要将析构函数声明为虚函数。

模板

模板是将类型参数化的一种手段
函数模板不能被声明为类的成员函数。

有一点需要特别注意:当程序中没有函数模板存在时,一般重载函数的参数具有隐式类型转换功能。使用函数模板后,函数将不再有机会执行隐式类型转换。因为当相同名字的函数模板和一般重载函数同时存在于程序中时,系统只进行了两种模式的参数匹配:
(1)、第一,寻找完全匹配的重载函数,如果没有则转入第二步。
(2)、第二,利用函数模板寻找完全匹配项,如果没有则报错。
显然,在这两中模式的匹配中,没有隐式类型转换的机会。

使用类模板生成模板类和类对象的一般语法格式如下:
类模板名 <数据类型实参表> 对象名(值实参表);

其它

内联函数

调用函数需要一定的开销,如果有的函数需要频繁调用,那么可能会带来比较大的开销,从而降低程序的执行效率。C++提供了内联函数来减少函数调用所带来的开销,即将函数的代码嵌入到函数的每一个调用处,虽然这样会使程序变得更长,但它却可以避免调用函数。
请注意,使用内联函数虽然可以减小开销,但会增加程序长度,因此inline关键字只适用于短小的且调用频繁的函数。

引用

运算符“&”只有在其前面有类型符相连时才代表“引用”,否则代表“取地址”。例如:
int &j=i; // “&”前面和int类型符相连,因此它在此处代表“引用变量j的声明”

p=&j; // “&”前面没有类型符,因此它在此处代表“变量j的地址”

系统不会为变量的引用另外分配存储空间,引用和被引用的变量共享同一内存单元

普通引用时,引用和被引用的数据类型不同会报错;
但当const引用也就是常引用和目标变量类型不一致时,编译器会创建类型正确的临时变量或者叫匿名变量来保存,即新定义这个变量的类型与目标变量相同,而不是定义时前面的类型名!!!

构造与析构函数的顺序

教材内容
构造:先做继承的
析构:先做自己的

构造
1.派生类构造函数初始化的执行顺序是:先调用基类构造函数初始化基类数据成员,如果有子对象随后进行子对象的初始化,最后初始化派生类新增成员
2.当派生类有多个基类时,处于同一层次的各个基类的构造函数的调用顺序取决于基类的声明顺序(自左向右)。

析构
在执行派生类的析构函数时,基类的析构函数也将被自动调用,析构派生类对象的步骤顺序是:先调用派生类的析构函数析构派生类对象新增部分,如果该派生类含有子对象,接下来就析构子对象部分(参见教材3.6),最后调用基类的析构函数析构基类部分。总之,派生类的析构顺序恰好与构造顺序相反

class与struct的区别

class默认为private,而struct默认为public

输入输出

在C++中,输入输出流被定义为类。C++的I/O库中的类称为流类。采用流类定义的对象称为流对象。
I/O库中有如下常见的类:
(1)ios:抽象基类(不能直接用ios定义对象),由ios派生出类istream和类ostream。
(2)istream:通用输入流类,支持输入操作。
(3)ostream:通用输出流类,支持输出操作。
(4)iostream:通用输入输出流类,由类istream和类ostream派生,支持输入输出操作。
(5)ifstream:输入文件流类,由类istream 所派生,支持输入文件操作。
(6)ofstream:输出文件流类,由类ostream 所派生,支持输出文件操作。
(7)fstream:输入输出文件流类,由类iostream所派生,支持输入输出文件操作。
image
I/O库不同类的定义被包含在不同的头文件中,常用的流类的头文件有:
(1)iostream(或iostream.h):包含对输入输出流进行操作所需的基本信息,包括类ios,类istream,类ostream和类iostream的定义。
(2)fstream(fstream.h):包含用户管理的文件的I/O操作,包括类ifstream,类ofstream和类fstream的定义。

输入/输出运算符重载

类类型有时也可定义自己的输入/输出运算符,以满足其特殊需求。即输入/输出运算符也可以重载,但都只能重载为非成员函数。
对于输出运算符“<<”来说,重载函数的函数名为:operator<<。它的第一操作数必须是标准输出类ostream对象的引用,比如:ostream &cout。它的第二操作数必须是本类的对象。为了保证输出运算符<<的连用性,重载函数的返回值类型应该为标准输出类ostream的引用。其语法格式如下:

ostream &operator << ( ostream &cout, 类类型 obj ); //重载输出运算符<<

对于输入运算符“>>”来说,重载函数的函数名为:operator>>。它的第一操作数必须是标准输入类istream对象的引用,比如istream &cin。它的第二操作数必须是本类的对象。为了保证输入运算符>>的连用性,重载函数的返回值类型应该为标准输入类istream的引用。其语法格式如下:

istream &operator >> ( istream &cin, 类类型 &obj ); //重载输入运算符>>

系统定义的输入输出函数只能输入输出标准数据类型数据(即C++的基本数据类型数据)。程序员可根据自己的需要,通过重载输入输出运算符函数来完成特定的功能。它甚至可以直接输入输出一个对象。输入输出运算符重载的实现格式参见例4.11。
注:输入、输出运算符必须重载为非成员函数,且一般应声明它是类的友元函数以便它访问类的私有成员。

类类型转换函数

转换函数是⽤户定义的强制类型转换,可以将类类型转换为其他类型。转换函数的定义如下:
  operator typeName();``   例如:operator double();``
  转换函数必须满⾜如下⼏点:
(1)转换函数必须是类⽅法
(2)转换函数不能指定返回类型
(3)转换函数不能有参数

class Integer
  {
  public:
    operator int() const;
  private:
    int m_nValue;
  }
  使⽤Interger时:
  Interger objInt;
  int nValue = objInt;   //可以直接将Interger类型对象赋值给int型变量

出处链接

基类对象不能赋值给派生类,但派生类对象可以赋值给基类

posted @   sz[sz]  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示