C++面向对象期末复习
课程属性:通选课
授课老师:刘玉祥
考试信息:单选(概念,类的四大特征、地址计算的优势)、填空(补全程序)、简答(概念)、设计(用类、多线程实现、怎么访问一个(不同继承方式),访问private变量)。构造函数顺序,代码区和数据区,堆区。都在ppt里。一级指针和二级指针的区别。指针和类是重点。
1.概述
低级语言:机器语言、汇编语言
高级语言:C,C++
翻译:将一种语言编写的程序转换成等效的另一种语言的过程(编译、解释)
解释:不生成目标程序。java是编译加解释的混合模式。
c++对c的增强
- 保持了C的简洁、高效和接近汇编语言等特点;
- 类型检查更为严格。
- 增加了面向对象的机制。
- 增加了泛型编程的机制(C++通过Template实现)。
- 增加了异常处理。
- 增加了运算符重载。
- 增加了标准模板库(STL)
编程方法上 c++是设计 c是算法
程序结构上 c是数据加算法 c++是对象加消息
预处理命令:宏定义、文件包含、条件编译一般是#开头
抽象、继承、多态、封装
2.编程基础
保留字:char, int
标识符:用户定义,字母 数字 下划线组成 字母或者下划线开头
分隔符:空格回车
底层const:const int * 指针可以指向不同的地址,但不能通过指针修改所指向对象的值
decltype的推断结果会受表达式“值类别”(左值 / 右值)影响,而auto不受此影响。
auto 会忽略引用属性和顶层const decltype不会,decltype会受到括号影响
看 const 与 * 的位置:
const 在 * 左边:修饰“指向的对象”→ 底层 const(如 const int* p 或 int const* p);
const 在 * 右边:修饰“指针本身”→ 顶层 const(如 int* const p)。
auto&会推导出一个引用类型的变量,该变量会直接绑定到初始化表达式的对象
若初始化表达式是右值(如字面量、临时对象),auto&无法绑定(编译错误),需用const auto&:
typedef
typedef int NUM[100];//声明NUM为整数数组类型,可以包含100个元素
NUM n;//定义n为包含100个整数元素的数组,n就是数组名
#define是编译预处理命令,只能做简单的字符替换
typedef是编译时处理的,声明一个类型替代原有的类型
运算:优先级 自左向右
% 运算 -7%2 = -1
! = ==的优先级比< >低,然后是运算
逻辑运算最低(|| && !)
int&&右值引用
赋值运算符以及复合赋值运算符的运算优先级
比算术运算符优先级低,运算结合性为自右向左。
条件运算符的优先级别高于赋值运算符,低于关系运算符和算术运算符
取反是对补码取反
函数
先计算后赋值
函数声明:函数原型
传递方式:按值、地址、引用
按值:形参与实参占用不同的内存单元
栈、堆(程序员手动分配释放)、静态存储区
函数调用的过程
递归、递推
根据作用域分为全局变量和局部变量
根据存储特性(生存期)分为静态存储变量和动态存储变量
全局变量的作用范围从定义变量的位置开始到本程序结束
在同一个源文件中,如果全局变量与函数的局部变量同名,在函数的局部变量的作用域内,同名的全局变量无效。如果要使用可以使用作用域作用符::
auto 默认,动态区
static 静态区 只会赋值一次
register CPU内部 动态区
extern 静态存储区
静态全局变量只会被本文件使用。
内联函数inline主要是解决程序的运行效率:在编译程序时,将函数代码直接插入到调用处,减少函数调用次数,以空间换时间
重载函数的形参必须不同:个数不同或类型不同。
默认形参值必须从右向左顺序声明
void func(int a,int b=2,int c=3,int d=4);
当函数既有声明又有定义时,默认参数在函数声明中定义,函数定义中不允许使用默认参数
指针
任何一个指针变量本身数据值的类型都是unsigned longint,占4(8)个字节
空类型(void)指针,即不指定指针指向一个固定的类型,其定义格式为:void *p;– 表示指针变量p不指向一个确定的类型数据,它的作用仅仅是用来存放一个地址
p±n 表示 p 向后/ 向前移动 n 个元素位置。对于某种数据类型的指针p来说:– p+ n的实际操作是:(p)+ n**sizeof(数据类型);– p - n的实际操作是:(p) - n ** sizeof(数据类型)
px-py运算的结果值是两指针指向的地址位置之间的数据个数。– 两指针相减实质上也是地址计算。– 它执行的运算不是两指针存储的地址值相减,而是按下列公式得出结果:((px)-(py))/数据长度
数组名就是一个常量指针
指针数组 和 数组指针
int (*p)[5];该语句声明了一个指向具有5个元素的int型数组的指针。

返回指针类型的函数, 和函数指针
函数指针:int (*p)(int,int);定义了一个函数指针p,它指向一个返回整型值、有两个整型参数的函数
(*p)(a,b),它相当于fun(a,b)
– 声明b是a的引用时,b本身不是变量,它是某个对象的别名,不会为b另外开辟存储单元,而是使b和a占内存中的同一存储单元– 在声明一个引用变量时,必须同时对其进行初始化。
结构体
结构体类型不分配任何存储空间
结构体变量中各成员在内存中的存储遵循字节对齐机制:
1 结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
2 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节。
3 结构体的总大小为结构体最宽基本类型成员大小的整数倍。
联合体Union
成员共用同一组内存单元。任何两个成员不会同时有效
联合体的初始化:只允许对其第一个成员进行初始化
类与对象
封装性、抽象性、继承性和多态性
私有成员:只允许类内成员函数存取它
公有成员:允许类内和类外函数存取它
保护成员:允许类内和其派生类函数存取它
自身类的对象不可以作为成员。但可以用指针。
每个对象的存储空间:– 仅仅指的是该对象数据成员所占用的存储空间– 并不包括成员函数所占用的空间

构造函数必须放在public部分,可以重载。可以有默认形参。
构造函数:不能被继承、不能为虚函数、不能显式调用、不能取构造函数的地址。
若已经定义了构造函数(不论它是否为缺省构造函数),则编译系统不再自动生成缺省构造函数
拷贝构造函数功能:– 用一个已定义并初始化的对象进行同一类的其他对象的构建及初始化。

与普通构造函数形式不同。
• 实参类型不同,拷贝构造函数的实参是对象。
• 普通构造函数在建立对象时被调用,拷贝构造函数在用已有对象复制新对象时被调用。
函数调用的时候,如果形参为类对象,会调用拷贝函数
使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生拷贝构造。
• 如果没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的拷贝构造函数
这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值。• 一般情况下使用编译器默认的拷贝构造函数就可以了,不需要自己写拷贝构造函数。• 特殊情况下,如需在拷贝的同时修改数据成员的值、或需实现深拷贝等,则需自己编写(编译器默认的拷贝构造函数不能实现这些功能)
深拷贝
当类的数据成员中有指针类型时,就必须定义一个特定的拷贝构造函数,该拷贝构造函数:– 不仅可以实现原对象和新对象之间数据成员的拷贝,– 而且可以为新的对象分配单独的内存资源,– 这就是深拷贝构造函数。
多个对象处于同一作用域的时候,先构造的对象后析构
析构函数不允许重载。没定义的话,会生成一个析构函数
静态对象不会释放直到程序结束
类组合
对于类组合:– 先调用内嵌对象的构造函数(按内嵌时的声明顺序,先声明者先构造)– 然后调用本类的构造函数
前向引用声明

类是一个不完全类型(incompete type),即已知该类是一个类型,但不知道包含哪些成员。只能使用被声明的符号,不能涉及类的任何细节。
系统不仅为对象数组分配内存空间,以存放数组中的每个对象,而且还会自动调用匹配的构造函数。
• this指针是隐含在类的每一个非静态成员函数中的一个对象指针,它是一个指向正操作该成员函数的对象
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
类的定义实现类内数据共享和保护
静态成员和友元实现类外数据共享,常量的定义实现数据保护。
静态成员解决同类不同对象之间数据的共享
友元实现不同类和对象之间数据的共享
静态数据成员是类的属性,这个属性不属于类的任何对象,但所有同类的对象都可以访问和使用它
而当类中定义了静态数据成员时,在编译时,就要为类的静态数据成员分配存储空间。
1.静态数据成员不能在类的构造函数中初始化,也不用参数初始化表对静态数据成员初始化。2. 静态数据成员也不可在类体内进行赋初值。3. 静态数据成员的初始化工作只能在类外,并且在对象生成之前进行;进行一次且只能作一次定义性说明。
int Test::priStatic = 30;
静态成员函数

友元函数不是当前类中的成员函数,它既可以是一个不属于任何类的一般函数,也可以是另外一个类的成员函数。友元函数可以访问该类中的所有成员
当一个类作为另一个类的友元时,就意味着这个类的所有成员函数都是另一个类的友元函数 。
友元关系不可以被继承。• 友元关系是单向的。– 若类A是类B的友元,不等于类B是类A的友元。• 友元关系不能传递。
静态数据成员属于类而非对象,因此常对象不限制静态成员的修改
常对象不能调用该对象的非const型的成员函数
常成员函数:函数内部不能有修改数据成员的操作,也不能调用其它非const成员函数。
<返回值类型> 函数名(参数表)const;
常引用能够保证数据安全,使数据不能被随意篡改
指向常量的指针、指针常量、指向常量的常量指针。
指向常量的指针:不能修改指向对象的值,但是指针可以指向其他地址。
指针常量:指针本身不可以修改,但是可以修改指向的对象值
指向常量的常量指针:既不能修改指针的指向,也不能通过指针修改指向的对象值(双重只读)
继承
当派生类与基类有相同的成员时,实现对基类成员的隐藏。若要在派生类中使用基类同名成员,可以显式地使用类名限定符:类名 ::成员

公有继承:基类的public和protected成员在派生类中的访问权限保持不变,基类的private成员在派生类中不可直接访问

私有继承:基类的public和protected成员在派生类中全部变为private权限,基类private成员仍不可
直接访问

保护继承:基类的public和protected成员在派生类中全部变为protect权限,基类private成员仍不可
直接访问

成员包括函数
基类定义的静态成员,将被所有的派生类所共享,并且基类和派生类共享基类中的静态成员。且静态成员不受继承方式影响,都可以直接访问。
构造函数和析构函数是不能被继承的
派生类的构造函数调用顺序:基类→派生类
基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数途径。
如果派生类构造函数的初始化列表显式指定了基类构造方式(如 C(int i,int j):B(i), c(j)),则调用指定的基类构造;
• 如果未指定(如 C()),则默认调用基类的无参构造函数(若基类没有无参构造,会编译报错)。
析构函数的顺序相反。
有子对象的派生类的构造函数应该需要完成
构造顺序遵循 “先基类 → 再子对象 → 最后派生类自身构造函数体”:
Student(基类 / 0 级)→ Student1(1 级派生类)→ Student2(2 级派生类)
Student2 不仅继承了 Student1 的成员,还间接继承了 Student 的成员。
多级派生的构造函数调用遵循 “从顶层基类到最底层派生类” 的顺序;
派生类的构造函数初始化列表只需指定直接基类 的构造方式,无需关心间接基类
声明派生类时如不写继承方式,其默认的继承方式是private
在多继承的情况下,多个基类构造函数的调用次序 是按基类在被继承时所声明的次序从左到右依次调用,与它们在派生类的构造函数实现中的初始化列表出现的次序无关。
二义性
同名二义性:使用作用域标识符、使用同名覆盖的原则

虚基类:
将公有派生类对象赋值给基类对象,反之是不允许
指向基类对象的指针变量也可以指向派生类对象。但是此时,通过指向基类对象的指针,我们只能访问派生类对象中继承过来的基类成员,访问不到新增的成员
多态
(编译连接阶段完成)静态关联:函数重载、运算符重载
动态关联:继承关系、虚函数

运算符重载通过成员函数或者友元函数来实现。
<函数返回值类型> operator <重载运算符>( [<参数列表>] )
定义运算符,由左操作数调用右操作数,最后将函数返回值赋给运算结果的对象
第1个运算数是对象,第2个运算数是参数
在类外定义:
<函数返回值类型> <类名>::operator <重载运算符> ( [<参数列表>] )
友元函数实现:将运算符的重载函数定义为友元函数,参与运算的对象全部成为函数
参数。
friend <返回值类型> operator <重载运算符> ( [<参数列表>] )
<返回值类型> operator <重载运算符> ( [<参数列表>] )

自增的实现
上面是前++,后面是后++

多了一个int型的参数,增加这个参数只是为了与前置自增运算符重载函数有所区别,此外没有任何作用。
友元函数的情况:

第一个参数必须是ostream &的类型,即ostream对象的引用

虚函数
指向基类的指针或引用来访问基类或派生类对象的成员函数时,将在运行时决定调用哪个函数,不会发生动态调用错误,这就体现了面向对象程序设计的动态多态性,也叫动态联编。
虚函数是可以继承的。
纯虚函数
virtual 函数类型 函数名(参数表) = 0;
如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类。
输入输出流
– 文本流—- 一串 ASCII 字符。
– 二进制流—- 按二进制格式存放的数据

文件重定向
freopen
二进制文件的读写

文件的随机访问

模版
模板语法使得“数据类型”也可以以参数的形式出现

实例化T的各模板实参之间必须保持完全一致的类型
类模版
如果在类外定义都要冠以模板参数说明,并且指定类名时要后跟类型参数。
template <模板参数表>
返回值类型 类名<模板参数标识符列表>::函数名(参数表)
类模版使用Compare
仿函数:重载了operator()
lambda
【值拷贝的方式】拷贝到线程栈空间中的,就算线程函数的参数为引用类型,
在线程函数中修改后也不会影响到外部实参,
- 如果想要通过形参改变外部实参时,必须借助std::ref()函数
- 地址的拷贝
- lambda表达式,在捕捉列表中添加a的引用