第一章 C++的初步认识
-
类是C++新增加的重要数据类型,可以体现数据的封装性和信息隐蔽。
-
封装:把有关数据与操作组成一个单位,与外界相对隔离。
-
大多情况下,将类中所有数据指定为私有,以实现信息隐蔽。
-
有类,可实现面向对象程序设计方法的封装、信息隐蔽、继承、派生、多态等功能。
-
-
声明语句即可放在函数内(作用范围是局部的,只在本函数内有效)也可放在函数外(作用范围是全局的)。
Eg:将int max(int a, int b); 函数声明放在main函数中
-
编译——编译器将源程序.cpp翻译为二进制形式的目标程序.obj,会进行“词法检查”“语法检查”。会产生“警告”和“错误”。
运行——将编译得到的目标程序输入内存,并与系统提供的库文件等连接,得到可执行文件.exe,最后将exe文件调入内存后运行。
-
词法检查:检查源程序中的单词拼写是否有误
-
语法检查:根据源程序的上下文检查程序的语法是否有错
-
警告:不影响运行的轻微错误
-
错误:必须改成,否则无法生成目标程序
-
第二章 - 第七章
- 函数不能嵌套定义,但可以嵌套调用(即在一个函数中调用另一个函数),形参不占内存存储单元,指定默认值的参数必须放在形参的最右端,在声明时给出。
如:函数声明为Box(int, int=10, int=10);
Box(15);则为15,10,10 //从左往右给值
-
函数重载:同名函数的形参(个数、顺序、类别)必须不同。
-
全局变量放在静态存储区,空间在整个程序运行期间不释放;动态存储区存放——
-
函数形式参数:在调用函数时给形参分配存储空间
-
函数中定义的变量:未加static的局部变量
-
函数调用时的现场保护和返回地址
在函数调用时分配动态存储区空间,函数调用结束后释放这些空间。
-
-
C++中的存储类别:
声明 存储类别 注解 auto 自动局部变量 函数中的局部变量(可省略,默认的) static 静态局部变量 存储在静态存储区 register 寄存器变量 将使用频繁的变量放在寄存器中,能不用从内存中提取,(现在编译器都有这个功能,不用自己声明) extern 外部变量 在一个文件中声明全局变量(让定义点前的函数也可以使用),在多个文件的程序中声明外部变量 -
C++编译系统将形参数组名一律作为指针变量处理。
-
行指针指向一行而非一个元素,列指针指向一行中的某个元素。
-
a[1][2]内容可用*(*(a+1)+2)表示,其中a+1表示行指针,*(a+1)表示列指针a[1]
-
列指针a[1]的行指针是&a[1]
-
-
const指针
类型 性质 格式 指向常量的指针变量 不能通过指针改变指向对象的值,可改变指针的值 const int* p; 常指针 能通过指针改变指向变量的值,不能改变指针的值 int* const p; 指向常量的常指针变量 二者都不能改 const int* const p; -
引用是变量的别名,所以常量不能使用引用,引用本身是个指针,其中存放变量的地址。
-
结构体中不能定义自身,但可定义自身的指针变量,不能在结构体中对成员变量赋初值。
-
结构体可定义构造函数,但不能定义析构函数;结构体不能继承;结构体可不使用new就能实例化,但类对象一定要new。
-
结构体类型相当于一个模型,但无具体数据,系统也不分配实际内存单元。
-
将结构体变量作为形参,也是值传递,改变它的值后,跳出函数没有变化,所以要想改变值,要使用结构体变量的指针或引用。
-
enum 枚举类型名 {枚举常量表};
-
枚举元素是常量,不能对其进行赋值,但是它是默认有值的,编译系统按照定义顺序,对其赋值0,1,2····,这个值可以输出
-
可在声明枚举时,指定枚举元素的值
enum weekday {sun=7, mon=1, tue, wed, thu, fri, sat}; //则tue=2,wed=3…
-
-
异或^ :相异为1,相同为0。 1^1=0
第八章 类和对象的特征
-
面向对象的程序设计4个特点:抽象、封装、继承、多态
-
类对象体现了抽象和封装的特征
-
封装:把有关数据与操作组成一个单位,与外界相对隔离。将对象的内部实现与外部行为隔离开来。【不能把类中的全部成员与外界隔离,一般把数据隐藏private,把成员函数作为外界的接口public,一些只有内部调用的工具函数为private】
-
继承和重用:C++提供继承机制,采用继承的方式能利用一个已有的类建立一个新的类,这样就能重用已有类的一部分。
-
多态性:由继承而产生的不同的派生类,其对象对同一消息会做出不同的响应。
-
-
类的声明与结构体的声明都以分号结束
-
若在类中定义中既不指定public也不指定private,则默认private
-
一个类中可有多个public、private部分
-
若一个类中不含成员函数,就等同于结构体,则无法体现出类在面向对象中的作用
-
内置成员函数,若 类体中定义 的成员函数不包含循环控制等结构,则自动作为内置函数处理
(inline void display(){…},inline关键词可写可不写)
-
若在类外定义,则需在声明或定义时显式inline声明,但这样不利于类的接口.h和类的实现.cpp相分离,不利于信息隐蔽
inline void Student::display(){}
-
内置函数:在调用inline声明的函数时,将函数的代码段复制并插入到调用点
-
-
每个对象占用的存储空间只是该对象的数据成员占用空间,不包括函数成员占用空间,成员函数存放在对象外的空间
第九章 怎样使用类和对象
-
类与结构体一样,默认生成无参构造函数,自定义有参构造函数后会覆盖此无参构造函数。类与结构体的构造函数写法、简写、重载都一致。对象数组与结构体数组写法一致。
-
默认构造函数——构造函数的所有参数都有默认值
若Box():height(0),weight(0){}; 若Box(int h=0, int w=0):height(h),weight(w){}
调用Box a = new Box(); //调用默认构造函数-
⚠️ 与系统的默认无参构造函数定义变量不同
Box b = new Box;
-
-
析构函数名字:类名前加~。
-
析构函数不能被重载,因为没有函数参数。
-
构造函数可有多个,但析构函数只能有一个;若用户没有定义析构函数,则系统会默认生成,但它只有析构函数的名称和形式,实际上什么操作也不执行(和默认无参构造函数一样,只是一个空函数)
-
通常不用写析构函数,当类的数据成员为指针且关联动态派生空间时,才需手动添加析构函数。
class ex2 { public: ex2(int v) {x = new int (v);} void setx(int v) {*x = v;} int getx() {return *x;} ~ex2() {delete x;} private: int* x; }
-
-
先构造的后析构,后构造的先析构(相当于一个栈,后进先出)
- 若在函数中定义静态局部对象,则只在程序第1次调用此函数时调用一次构造函数,在调用函数结束时对象并不释放,故也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。
-
this指针:*this表示被调用的成员函数所在的对象
- 所谓 “调用对象a的成员函数f” 实际上是在调用成员函数 f 时,使 this 指针指向对象a,从而访问对象a的成员。
-
公用数据的保护
类型 定义 常对象 类名 const 对象名 / const 类名 对象名 常数据成员 const 数据类型 变量名(定义与常变量相同) 常成员函数 类型名 函数名 (形参列表) const -
常对象的成员函数不一定是常成员函数(不加const默认为普通函数),但数据成员一定是常数据成员。
-
常对象只能调用其中const成员函数,不能调用非const成员函数。
-
const成员函数能访问非const/const数据成员、常对象,但都不能改变它们的值。
-
-
const指针
类型 格式 指向常对象的指针 const 类名* p; 指向对象的 常指针 类名* const p; ⚠️ 常指针不管是指向变量还是对象,都是const写里面
-
若一对象被声明为常对象,则只能通过指向常对象的指针指向它
-
指向常对象的指针常用于形参,目的是保护形参指针指向的对象,使之在函数执行过程中不被改变
-
对象的复制:从无到有建立一个新对象,并使它与已有对象完全一致。会调用复制构造函数,作用就是将实参对象的各成员值赋给新对象的成员。
-
复制构造函数 只有一个常引用参数,若未定义,则系统会提供默认的
Box(const Box& b){…}
- 类名 对象1 (对象2)【调用复制构造函数】
- 类名 对象1 = 对象2【调用默认复制构造函数】
-
对象的赋值:对一个已存在的对象赋值。类数据成员不能包括动态分配内存的指针成员,否则可能在delete的时候出错。
- 对象1 = 对象2
-
静态数据成员需在类体内数据成员声明时加static,并在类外进行初始化(只能在类外)
int Box::height = 10;
-
不能用参数初始化表对静态数据成员初始化
-
若未对静态数据成员赋初值,则自动赋初值0
-
public的静态数据成员,可通过对象名访问,也可通过类名访问;
Box::height; b1.height;
而private的静态数据成员只能通过成员函数访问
-
静态数据成员的作用域只限于该类的作用域
-
-
静态成员函数:static int volume();
- 没有this指针
- 不能访问一个对象中的非静态成员,只能访问静态数据成员
-
友元函数:在类体中对函数用friend声明
-
在友元函数中引用私有成员时,要加上对象名,因为 友元函数没有this指针,故要指定要访问的对象。
friend void display (Box &b);
-
-
友元类:friend 类名;
第十章 重载运算符
-
函数重载:同名函数的形参(个数、顺序、类别)必须不同。
- 类定义中的函数重载:构造函数重载、赋值运算符重载
-
赋值运算符重载:函数类型 operator 运算符名称 (形参表) {对运算符的重载处理}
complex operator + (const complex& left, const complex& right){…}作为友元函数;
也可只写一个参数的作为类成员函数,此时调用的第一实参一定要是类对象 ,Eg:c+2。-
重载运算符的函数不能有默认的参数
-
重载运算符参数至少应有一个是用户自定义的类对象(或类对象的引用)
-
重载流插入运算符“<<”与流提取运算符“>>”的函数只能作为友元函数,不能作为成员函数,因为函数的左操作数一定要是ostream/istream类对象;第一参数与返回值都是ostream/istream类的引用,是为了保证连续输入输出。
ostream & operator << (ostream& out , 自定义类& b){ out << b.height;}
istream & opetaror >> (istream& , 自定义类&){…}
-
-
转换构造函数:将其他基本数据类型/类的数据转换为该类的对象,只能有一个参数。
Complex(double r){real=r; imag=0;}
- 默认构造函数、自定义构造函数、复制构造函数、转换构造函数都是构造函数的重载。
-
类型转换函数:operator 类型名(){实现转换的语句}
Complex类中定义operator double(){return real;} //将Complex对象转换为double类型。
-
只能作为成员函数,将本类的对象转换为“类型名”所示类型
-
没有形参,返回值由类型名决定
-
-
谨慎使用转换构造函数及类型转换函数,若都写可能造成二义性
c1+2.5; //c1是Complex对象
将c1根据类型转换函数化为double 还是将2.5根据转换构造函数化为Complex对象?
第十一章 继承和派生
- 若B继承A,称A为B的父类、基类、超类;称B为A的派生类、子类。
class 派生类名:[继承方式] 基类名
{派生类新增的成员} ;
// 继承方式——public、protected、private。不写默认private
-
派生类会继承基类所有成员,除构造函数和析构函数
-
若继承的成员与派生类新增的成员同名,则会被派生类新增的成员覆盖
- 若是函数,要函数名、形参(个数、类型、顺序)都相同
-
-
访问控制(私有成员只能在本类中访问)
基类成员访问控制 继承访问控制 在派生类中的访问控制 public public protected public protected private 不可访问 public protected protected protected protected private 不可访问 public private protected private private private 不可访问d -
派生类对象实例占有存储空间,其中存放派生类中定义的非静态数据成员+从基类中继承下来的非静态成员
-
静态数据成员、成员函数是整个类中存储一份
-
Eg:若类A中有静态数据成员,B继承A
则B中继承A的静态数据成员的存储空间仍在A中,只有B中定义的静态数据成员的存储空间在B中
-
-
简单派生类构造函数(派生类不包含类对象【子对象】)
基类:Student (int n, string nam, char s): num(n), name(nam), sex(s) {}
派生类:Student1(int n, string nam, char s, int a, string ad): Student(n, nam, s), age(a), adds(ad) {}
可见派生类需调用基类的构造函数⚠️ 此时基类的数据成员不能定义为private,否则派生类没办法访问
-
派生类构造函数
-
调用 基类 构造函数
-
执行 派生类 构造函数
-
-
派生类析构函数【调用顺序与构造函数相反】
-
执行派生类析构函数
~Student1()
-
执行基类析构函数
~Student()
-
-
-
有子对象的派生类构造函数
基类:Student (int n, string nam): num(n), name(nam) {}
若在 Student1中还定义了Student monitor; 子对象,则派生类构造函数写法:
Student1(int n, string nam, int n1, string nam1, int a, string ad): Student(n, nam), monitor(n1, nam1), age(a), adds(ad) {}⚠️ 基类构造函数与子对象的写法次序是任意的
- 派生类构造函数
-
调用 基类 的构造函数,对基类数据成员初始化
-
调用 子对象 的构造函数,对子对象数据成员初始化
-
执行 派生类 的构造函数,对派生类数据成员初始化
-
- 派生类析构函数【调用顺序与构造函数相反】
-
执行派生类的析构函数,对派生类新增的成员进行清理
-
调用子对象的析构函数,对子对象进行清理
-
调用基类的析构函数,对基类进行清理
-
- 派生类构造函数
-
多层派生时的构造函数
⚠️ 不用列出每一层派生类的构造函数,只需写出其上一层派生类的构造函数即可
基类:Student (int n, string nam): num(n), name(nam) {}
派生类 Student1:Student1 (int n, string nam, int a): Student (n, nam),age(a) {}
派生类 Student2:Student2 (int n, string nam, int a, string ad): Student1 (n, nam,a),addr(ad) {} -
多重继承
class 派生类名:[继承方式] 基类名,[继承方式] 基类名,[继承方式] 基类名··· {派生类新增的成员} ;
-
构造函数
派生类构造函数名(总参数表): 基类1构造函数(参数表), 基类2构造函数(参数表)···
{派生类中新增数据成员初始化语句}
-
-
多重继承引起的二义性问题,需指明作用域
-
继承的基类有同名成员
如基类 Teacher、Student中都有数据成员name
派生类 class Graduate: public Teacher, public Student {···}
此时若派生类中若要获得从Teacher中继承的name,写法:Teacher::name
若派生类外定义 g1对象,则 g1.Teacher::name -
基类的派生类有同名成员
如基类 Teacher与其派生类 Graduate都有数据成员name
则派生类中 name 获得派生类成员【同名覆盖】,Teacher::name 获得基类成员
若派生类外定义 g1对象,则 g1.name 获取派生类成员,g1.Teacher::name 获得基类成员 -
继承的基类A、B是从同一基类派生而来
如派生类 C继承 A、B,A、B继承自二者的基类 N,N中有一数据成员 a
则 C中通过 A::a 获取 A自N继承的a,通过 B::a 获取自N继承的a,而不用N::a
若派生类外定义 c1对象,则 c1.A::a 获取 A自N继承的a
PS:同名覆盖:数据成员同名,函数成员同名且形参(类型、个数、顺序)都相同
-
-
虚基类:使在继承间接共同基类时只保留一份成员
// 虚基类是在声明派生类时,指定继承方式时声明的 class 派生类名: virtual [继承方式] 基类名 {派生类新增的成员} ;
若有基类A,B和C继承A,D继承B和C
若要D中只保留一份从A间接继承成员,不要B和C各有一份相同的成员,则需将A声明为需基类class A //声明基类A { A(int i):n(i){} //基类构造函数,有一个参数 ···}; class B: virtual public A. //声明B是A的公用派生类,A是B的虚基类 { B(int i):A(i){} //B类构造函数,在初始化表中对虚基类初始化 ···}; class C: virtual public A. //声明C是A的公用派生类,A是C的虚基类 { C(int i):A(i){} //C类构造函数,在初始化表中对虚基类初始化 ···}; class D: public B, public C { D(int i):A(i), B(i), C(i){} //D类构造函数,在初始化表中对所有基类初始化 ···};
-
以前,多层继承中派生类只需对直接基类初始化,再由直接基类负责对间接基类初始化
现在,由于虚基类再派生类中只有一份数据成员,故 这份数据成员的初始化必须由派生类直接给出
-
若此数据成员由直接派生类(B、C)给出,则可能出现二者对这份数据给出参数不同导致的矛盾
-
虽然初始化表写了A、B、C,但是只执行派生类D对虚基类构造函数的调用,不会执行B/C对虚基类构造函数的调用
-
之所以B、C还要写,是因为D可能还需对从B、C中继承的其他数据成员初始化
-
-
-
基类与派生类的转换
-
赋值兼容:不同类型数据之间的自动转换和赋值
- 基类和派生类对象之间也有赋值兼容关系,可进行类型间的转换
-
派生类对象可 对基类对象赋值
-
子类给父类赋值,会舍弃子类自己的成员
-
父类不能给子类赋值,子类之间也不能相互赋值,因为无法对子类自身的成员赋值
-
-
派生类对象可替代基类对象 向基类对象的引用赋值/初始化
-
若函数的参数是基类对象/基类对象的引用,相应的实参可用子类对象
-
派生类对象的地址可赋给指向基类对象的指针变量,即 指向基类对象的指针变量可指向派生类对象
⚠️ 派生类一定要是 公用派生类,且这样调用时,都是 调用派生类中的基类成员,无法访问派生类的其他成员。
-
-
类的组合:一个类以另一个类的对象作为数据成员
-
通过继承,建立了派生类、基类的关系【纵向】
-
通过组合,建立了成员类、组合类(复合类)的关系【横向】
-
第十二章 多态性和虚函数
-
多态性分类
分类 含义 实现 效果 静态多态性
(编译时多态性)在程序编译时系统就能决定要调用那个函数 函数重载 具有不同功能的函数可用一个函数名,这样就可实现一个函数名调用不同内容的函数
(运算符重载实际也是函数重载)动态多态性
(运行时多态性)在程序运行时才能动态确定操作所针对的对象 虚函数 同一类族中不同类的对象,对同一函数调用作出不同的响应 -
函数重载:处理的是同一层次上的同名函数问题【横向重载】
- 指函数首部中,函数名相同,形参个数、顺序、类型有一不同
-
虚函数:处理的是不同派生层次上的同名函数问题【纵向重载】
- 指函数首部完全相同,包括函数名、形参(个数、顺序、类型)
-
-
虚函数:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数
-
作用:允许在派生类中重新定义与基类同名函数,并通过 基类指针或引用 访问基类和派生类的同名函数
(通过基类指针/引用 指向不同的派生类,系统会动态关联,调用相应类的同名函数)
-
若不采用虚函数,则相当于基类和派生类对象之间的赋值兼容,会调用派生类中的基类成员
-
当一个成员函数被声明为虚函数时,其派生类中的同名函数都自动成为虚函数,可以不加virtual,但为了代码清晰,最好加下。
-
故若有成员函数被声明为虚函数时,在同一类族中不能再定义一个非virtual的同名函数【指首部完全相同的那种】
-
若在派生类中没有对基类的虚函数重新定义,则派生类简单继承其直接基类的虚函数
-
-
类外的普通函数不能声明为虚函数,虚函数只能用于类的继承结构
-
-
构造函数不能声明为虚函数
- 因为执行构造函数时,类对象还未完成建立过程,则谈不上把函数与类对象的绑定
-
析构函数最好声明为虚函数
- 因为若采用派生类对象给指向父类的指针变量 p 赋值时,delete p;只会执行父类的析构函数,而不执行派生类的,所以我们需要将基类的析构函数声明为虚函数
-
若将基类的析构函数声明为虚函数,则由此派生的派生类析构函数自动转换为虚函数,即使派生类的机构函数与基类析构函数名字不同
-
即使基类不需要析构函数,也要显式定义一个函数体为空的析构函数,保证派生类撤销动态分配空间时能得到正确处理
-
纯虚函数:定义虚函数时不定义函数体,其作用只是定义一个虚函数名,具体功能留给派生类去添加
- 若在基类中没有保留函数名,则无法实现多态性
virtual 函数类型 函数名(参数列表) = 0; // 此为声明语句,最后有分号
-
最后的“=0”不表示函数返回值为0,只起到形式上的作用,告诉编译系统这是纯虚函数
-
纯虚函数没有函数体,只有函数名而不具备函数的功能,故不能被调用
-
抽象类(抽象基类):包含纯虚函数的类
-
因为纯虚函数不能被调用,包含纯虚函数的类无法建立对象
-
抽象类不以创建对象为目的,唯一作用是 作为一个类族的共同基类,为一个类族提供一个公共接口
-
若抽象类的 派生类没有对所有虚函数进行定义,则此派生类仍为抽象类
-
虽然派生类不能定义对象(即抽象类不能实例化),但可定义指向抽象类数据的指针变量。当派生类成为具体类后,可用抽象类的指针变量指向派生类对象,然后通过该指针调用虚函数,实现多态性。
第十三章 输入输出流
-
C++的输入输出
-
标准的输入输出(标准I/O):对系统指定的标准设备的输入和输出
-
文件的输入输出(文件I/O):以外存(磁盘、光盘)为对象进行输入和输出
-
字符串的输入输出(串I/O):以内存中指定的空间进行输入和输出
-
-
缓冲区的数据称为流,C++中输入输出流被定义为类
- I/O库中的类称为流类,用流类定义的对象称为流对象
-
C++的流库
类名 作用 头文件 ios 抽象基类 istream 通用输入流和其他输入流的基类 ostream 通用输出流和其他输出流的基类 iostream iostream 通用输入输出流和其他输入输出流的基类 ifstream 输入文件流类 ofstream 输出文件流类 fstream iofstream 输入输出文件流类 istrstream 输入字符串流类 ostrstream 输出字符串流类 strstream iostrstream 输入输出字符串流类 -
iomanip头文件:使用格式化I/O
-
ostream类定义的三个输出流对象【标准输出流】