期末面向对象复习大纲
C++ 面向对象程序设计 知识点总大纲
I. 基础语法与控制流 (C++ Basics, Ch 2)
A. C++ 概述与词法结构
- C++ 产生和发展历史(C++11/14/17/20 等标准)。
- C++ 语言的最小词法单元:关键字、标识符、文字(字面量)、操作符、分隔符、空白符 。
- 标识符命名规则(起始字符、组成、大小写敏感性)。
B. 数据类型、常量与变量
- C++ 基本数据类型:整数类型(
int,short,long, 符号/无符号)、浮点数类型(float,double)、字符类型(char, ASCII 编码)、布尔类型(bool), 。 - 常量分类:整数常量(十进制、八进制、十六进制、后缀)、字符常量、字符串常量(以
\0结尾), 。 - 变量定义与初始化(包括列表初始化)。
- 符号常量(
const)。 - 常量表达式(
const,值在编译阶段确定)。 - 类型别名(
typedef和using)。 - 枚举类型(
enum):不限定作用域枚举(默认值 0, 可关系运算, 整数不能直接赋值给枚举变量),以及enum class(枚举类) 的强作用域和类型转换限制 , , , 。 - 类型推断(
auto)和类型获取(decltype), 。
C. 运算符与表达式
- 算术运算、自增自减(
++,--)。 - 赋值运算(
=)和复合赋值运算符(+=等)。 - 逗号运算和逗号表达式(求解顺序及结果)。
- 关系运算
==,!=,<,>等)。 - 逻辑运算(
!,&&,||)及其 短路特性 - 条件运算符(
?:) sizeof运算和位运算(&,|,^,~,<<,>>- 运算符优先级与结合性
D. 流程控制与 I/O
if语句和嵌套if结构(if-else配对原则).switch语句(执行顺序、case值类型限制、break的作用), .while语句和do-while语句(执行顺序的区别), .for语句(包括基于范围的for循环).break和continue语句的作用 .- I/O 流概念(
cin,cout),插入符<<和提取符>>, . - I/O 流操纵符(如
endl,setw,setprecision).
II. 函数 (Functions, Ch 3)
- 函数的定义、调用、函数原型(声明)。
- 函数的嵌套调用 。
- 函数参数传递:值传递(单向)与引用传递(双向), 。
- 常引用作参数:保障实参数据的安全,防止被调函数意外修改实参 。
- 递归调用:定义、递推过程、回归过程,递归出口(终止条件)。
- 含有可变参数的函数(
initializer_list类型), 。 - 内联函数(
inline):作用(编译时替换)、限制(不能有循环/switch)、定义位置 。 - 带默认参数的函数:设置默认值、形参位置限制(必须在最右侧)、默认值设置位置(原型声明或函数定义), 。
- 函数重载:概念、重载规则(形参类型或个数必须不同)、不以返回值区分 。
- C++ 系统函数(如
cmath中的sqrt,sin,pow等).
III. 类与对象核心 (OOP Core, Ch 4)
A. 类和对象的定义
- 类是对象的抽象,对象是类的实例 , 。
- 类的语法形式、成员访问权限(
public,private,protected), 。 - 类内初始值(用于数据成员初始化)。
- 成员函数的定义:类中声明原型,类外定义实现(使用类名限定),内联成员函数 。
B. 构造函数与析构函数
- 构造函数:作用(初始化对象)、形式(与类名同名,无返回值),可重载、可带默认参数 。
- 默认构造函数:参数表为空或全部参数有默认值的构造函数;编译器隐含生成(如果未定义其他构造函数)或显式指示(
=default), 。 - 委托构造函数(避免重复代码)。
- 复制构造函数:形参为本类的常引用(
const 类名 &对象名)。 - 复制构造函数被调用的三种情况:
- 定义对象时以本类另一个对象作为初始值 。
- 函数的形参是类的对象(值传递)。
- 函数的返回值是类的对象 。
- 隐含的复制构造函数(成员依次复制)。
- 禁用复制构造函数(
=delete或private)。 - 析构函数:作用(清理工作)、形式(函数名加
~),在对象生存期结束时自动调用 。
C. 类的组合与结构体
- 类组合:类成员是另一个类的对象 。
- 组合类的构造函数设计:必须在初始化列表中对成员对象进行初始化 。
- 组合类成员的构造顺序:按照成员在类体中定义的顺序进行初始化(与初始化列表顺序无关), 。
- 结构体(
struct):特殊形式的类,默认访问权限为public,常用于只有数据且无复杂操作的类型 。 - 联合体(
union):成员共用同一内存单元,任何两个成员不会同时有效 。
IV. 数据的共享与保护 (Data Sharing, Ch 5)
作用域:函数原型作用域、局部(块)作用域、类作用域、文件作用域 , 。
可见性:内层作用域中声明的同名标识符会隐藏外层作用域的标识符 , 。
对象的生存期:
- 静态生存期:与程序运行期相同(全局变量、
static变量)。 - 动态生存期(局部生存期):限于块作用域中,离开作用域结束 。
- 静态生存期:与程序运行期相同(全局变量、
类的静态数据成员(
static):为该类的所有对象共享,具有静态生存期 。静态数据成员的声明与初始化(一般在类外使用
::初始化), 。- 类的静态变量是否全局可见?
关于类的静态数据成员(例如 int Cat::numOfCats)的可见性,需要区分其生存期和作用域:
• 生存期(Life): 它们具有静态生存期,从程序启动时存在到程序结束,因此它们在“时间”上是全局的。
• 可见性(Visibility): 它们不是像传统的全局变量那样“全局可见”。静态数据成员的声明是在类作用域内。
◦ 在类外部访问它们时,即使它们是 public 的,也必须通过类名和作用域操作符 :: 来访问(例如 CLIENT::GetServerName()),。
◦ 如果静态成员被声明为 private(为了封装),则它们只能通过该类的静态成员函数(例如 Cat::getNumOfCats())来间接访问,。
因此,更准确的说法是:类的静态变量具有全局寿命,但其可见性受到类的作用域限制,必须通过特定的方式(类名::)才能访问。
- 类的静态变量是否全局可见?
类的静态函数成员(
static):主要用于处理静态数据成员,可以直接通过类名::调用;不能直接访问非静态成员 , , 。友元机制:
friend关键字,破坏数据封装;友元函数(可访问私有/保护成员)、友元类(关系是单向的), , 。共享数据的保护(
const):常对象(必须初始化,不能更新),常成员函数(不更新对象数据成员),常引用(防止意外更改实参), 。常成员函数与非常成员函数可以重载 。
- 常数据成员举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27#include <iostream>
using namespace std;
class A {
public:
A(int i);
void print();
private:
const int a;
static const int b; //静态常数据成员
};
const int A::b=10;
A::A(int i) : a(i) { }
void A::print() {
cout << a << ":" << b <<endl;
}
int main() {
//建立对象a和b,并以100和0作为初值,分别调用构造函数,
//通过构造函数的初始化列表给对象的常数据成员赋初值
A a1(100), a2(0);
a1.print();
a2.print();
return 0;
}
//运行结果:
//100:10
//0:10多文件结构:类声明(.h)、类实现(.cpp)、主函数(main().cpp)。
编译预处理命令:
#include、#define(宏定义)、条件编译(#if,#else,#endif,#ifdef,#ifndef)。
1 | |
V. 数组、指针与动态内存 (Arrays & Pointers, Ch 6)
A. 数组
- 数组的定义(一维/多维)和元素访问 。
- 数组的存储:元素在内存中顺次连续存放 , 。
- 数组的初始化:部分初始化时未初始化的元素自动赋 0;局部非静态数组存在垃圾数据 , 。
- 数组名是常量,不可以被赋值。
- 数组作为函数参数:传递的是数组首地址(退化为指针),形参对实参数组的改变会直接影响实参数组 。
- 注意数组退化问题:对于形参来说,其实形参 int a[]与 int *p是等价的,数组在传入函数会被退化为首元素的地址/首行的地址;但是注意二维数组退化只会退化第一维,比如int\只能退化为int[]\,因为第二维也是编译器索引的依据!
- 对象数组:定义、初始化(调用构造函数)、访问 ,
- 定义:给出全部数据这时候可以不写出方括号里的具体数字,二维数组可以不写第一维具体数字,但二维数组的第二维一定要写,这是查找的步长依据
- 可以不给出全部所需元素的,但一定要写出所有具体数字
- 用[]访问,数组名本身就是首元素的地址,因此 int a[]; int *p=a;是对的;
B. 指针
指针概念:内存地址,用于间接访问内存单元;指针变量用于存放地址 。
数组名是数组首元素的内存地址,是一个常量,不能被赋值 , 。
指针的初始化与赋值:必须赋地址,不能是普通整数(除了 0 或
nullptr), 。**
nullptr**:C++11 引入的类型安全的空指针 。void*指针:可被赋予任何类型对象的地址,但解引用前需强制转换 。指向常量的指针(
const T *p):不能通过指针修改数据,但指针可指向其他对象1
2
3
4
5
6
7
8const int a=10;
int b=2;
const int *p1;
int *p2;
p1=a;//√
p2=a //X 常量变量只能被指向常量的指针指向
*p1=10; //X 错,指向常量的指针保证只对指向的变量可读,但是不能通过该指针修改指向的地址存储的值
p1=b; //√,但是指向常量的指针可以重新指向别的地址,而且不一定是const声明的变量指针常量(
T *const p):指针的值(地址)不能改变,但可以通过它修改所指对象(除非对象是const1
2
3
4
5const int a =10;
int b=2;
int const *p=b; //无所谓指向的地址存储的变量能不能改变,重点在于不能改变指向的地址
p=a;//×,不能改变指向的地址
*p=90; //√,改变地址存储的变量值,但是本身指向的地址没有变,合法指向包含
static关键字的变量,有static int * p指针的算术运算:指针与整数的加减(指向第 n 个数据的起始位置)、
++/--运算(指向下一个/前一个完整数据的起始)。- 注意,如果a是数组名,a++/a–是不合法的,正如前面所说,数组名其实就可以算作首元素的地址的别名,地址怎么运算?
指针数组:数组元素是指针类型 。
函数指针:指向程序代码存储区,用于实现函数回调 。
int compute(int a,int b,int (*func)(int,int))//注意要写上传入函数的形参和返回类型 { return func(a,b); } int max(int a,int b) { return a>=b? a:b; } int main() { //调用方法1,取函数地址 compute(1,2,&max); //调用方法2,声明一个指向函数的指针,传入指针 int (*maxPtr)(int,int);//形参的写法 maxPtr=max; compute(1,2,*maxPtr); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
4212. **对象指针**:通过 `类名 *对象指针名` 定义;通过 `->` 访问成员
1. (\*p).func() <=>p->func();
2. 利用对象指针可以弥补 `前向声明`的不足
```c++
#include <iostream>
using namespace std;
class B; //前向声明,只能表示有这个类,具体怎么样不知道
class A{
public:
void f(B,b);//√,这里只是声明函数没有让B的实例b进行具体的操作
void fPtr();
private:
B b; //错误,不能定义b,我们现在还不知道B的构造函数是什么样的
B* b;//可以,指向B类的地址,没有直接调用B的任何成员
};
class B{
public:
void g(A,a);
};
void A::f(B b)
{
cout<<"A::f called"<<endl;
}
void A::f()
{
b->gPtr();//注意这里函数的具体定义已经写在B类定义后了
}
void B::g(A a)
{
cout<<"B::g called"<<endl;
}
void B::gPtr()
{
cout<<"B::gptr called"<<endl;
}
int main()
{
}
13.this 指针:==隐含于类的每一个非静态成员函数中==,指向当前对象(成员函数所操作的对象,毕竟直接通过类名调用成员函数嘛,是通过实例),用于区分形参和成员变量,以及实现链式调用
1. 例, Point类中getX()函数中调用 return x;相当于 return this->x
2. 我们可以用来区分形参与成员变量,this->age=age;
3. 实现链式调用
1 | |
Builder& setX(int val) {
x = val;
return *this; // Enable chaining
}
Builder& setY(int val) {
y = val;
return *this;
}
void print() const {
Qstd::cout << "x: " << this->x << ", y: " << this->y << std::endl;
}
private:
int x = 0, y = 0;
};
int main() {
// Usage:
Builder b;
b.setX(10).setY(20); // Fluent interface
b.print();
return 0;
}
1 | |
1 | |
C. 动态内存分配与字符串
动态申请内存:
new T和new T[n](返回 T 类型指针), 。释放内存:
delete p和delete[] p(必须是new返回的指针), 。应用:动态创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39#include <iostream>
using namespace std;
class Point{
public:
Point() : x(0), y(0) {
cout<<"Default Constructor called."<<endl;
}
Point(int x, int y) : x(x), y(y) {
cout<< "Constructor called."<<endl;
}
~Point() { cout<<"Destructor called."<<endl; }
void move(int newX, int newY) {
x = newX;
y = newY;
}
private:
int x,y;
};
int main() {
cout << "Step one: " << endl;
Point *ptr1 = new Point; //调用默认构造函数
delete ptr1; //删除对象,自动调用析构函数
cout << "Step two: " << endl;
ptr1 = new Point(1,2); //调用传参构造函数
delete ptr1;
return 0;
}
/*
运行结果
Step One:
Default Constructor called.
Destructor called.
Step Two:
Constructor called.
Destructor called.
//如果是普通的实例对象,要再作用域语句执行完之后才会销毁对象
//使用new delete可以自己控制对象的生存期
*/应用:动态创建多维数组
例如:1
2
3
4
5
6
7
8
9
10
11
12char (*fp);//创建一个指向有三个char元素的指针fp
fp=new char;//创建一个2d数组,两个元素,返回第一个元素地址的指针;
//辨析
char (*fp);
//创建一个指向有三个char元素的指针fp,那么fp是一个指针名
char *fp;
//创建一个拥有三个char类型指针的元素的数组,fp代表第一个元素(char类型指针)的地址
char **fp /*生成指向char类型指针的指针(交错数组),
更加自由,用它构建的二维数组,可以不是一整块内存分配的,
而且数组的每一行的元素个数可以不尽相同*/
- 可以在访问数组元素前检查下标是否越界
- 用assert来检查,assert只在调试时生效
- 可以在访问数组元素前检查下标是否越界
智能指针(C++11):
unique_ptr(不共享资源)、shared_ptr(共享)、weak_ptr(不影响资源释放)。深层复制与浅层复制:浅层复制只复制指针本身,深层复制复制指针所指向的内容,避免二次释放和数据共享问题。
- 复制指针本身让两个指针指向同一个地址,如果通过其中一个指针修改变量,释放内存,另一个指针指向的值也会发生相应变化,
- 深层复制,是回到复制的精髓,我们真正要复制的是指针指向的对象的值,然后用一个新的指针指向一个有相同值的新的地址。就像我们拷贝变量也是在新的地址存储相同的值,不是复制该指针成员本身,而是将指针所指对象进行复制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37//例如6-18
……
~ArrayOfPoints() {
cout << "Deleting..." << endl;
delete[] points;
}
……
/*
运行结果如下Please enter the number of points:2
Default Constructor called.
Default Constructor called.Copy of pointsArray1:
Point_0 of array2:5,10
Point_1 of array2:15,20After the moving of pointsArray1
Point_0 of array2:25,30
Point_1 of array2:35,40
Deleting….Destructor called.
Destructor called.Deleting
…接下来程序出现运行错误。
先删掉了Array1,Array1的析构函数调用了Point类的析构函数,把array1和Array2的共同指向的Point 变量给删除了,后面Array2再删除自己的Point类成员,删除失败了
//只靠系统生成的复制构造函数不够,我们要显式构建一个,复制所指的对象
*/
//修改:新的复制构造函数
....
ArrayOfPoints(const ArrayOfPoints& v); //复制构造函数
.....
};
ArrayOfPoints::ArrayOfPoints(const ArrayOfPoints& v){
size = v.size;
points = new Point[size];
for (int i = 0; i < size; ++i) {
points[i] = v.points[i];
}
}案例:函数返回含有指针成员的对象,使用深层复制构造函数,将要返回的局部对象转移到主调函数,省去了构造和删除临时对象的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40#include <iostream>
using namespace std;
class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)) { // 构造函数
cout << "Calling constructor..." << endl;
}
IntNum(const IntNum &n) : xptr(new int(*n.xptr)) { // 复制构造函数
cout << "Calling copy constructor..." << endl;
}
IntNum(IntNum &&n) : xptr(n.xptr) { // 移动构造函数
n.xptr = nullptr;
cout << "Calling move constructor..." << endl;
}
~IntNum() { // 析构函数
delete xptr;
cout << "Destructing..." << endl;
}
int getInt() { return *xptr; }
private:
int *xptr;
};
// 返回值为 IntNum 类对象
IntNum getNum() {
IntNum a;
return a;
}
int main() {
cout << getNum().getInt() << endl;
return 0;
}
//运行结果
/*
Calling constructor...
0
Destructing...
*/移动构造(C++11):通过移动语义将临时对象的资源控制权转移给目标对象,减少不必要的复制,提高性能。
- 移动构造函数
class_name ( class_name && )
- 移动构造函数
string类:对字符数组操作的封装,重载了运算符(如+和比较运算符),解决了 C 风格字符串的缺点。- C 风格字符串:以字符数组存储,以
\0结尾,操作繁琐,易越界 。例如char str="program;char str = { 'p', 'r', 'o', 'g', 'r', 'a', 'm', '\0' }char str[] = "program"; - string 类常用构造函数
string(); string(const char* s); string(const string& rhs) - s[i] 访问串中下标为i的字符
- C 风格字符串:以字符数组存储,以
string类的输入:cin >> s以空格为分隔符;getline(cin, s)输入整行字符串 。- getline可以输入整行字符串(要包括string头文件),输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号),将分隔符作为getline的第3个参数即可,例如:
getline(cin, s2, ',');1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#include <iostream>
#include <string>
using namespace std;
int main() {
for (int i = 0; i < 2; i++){
string city, state;
getline(cin, city, ','); //<string>库里面
getline(cin, state);//输入的Beijing/San Francisco已经被前面的city获取了
cout << "City:" << city << “ State:" << state << endl;
}
return 0;
}
/*
运行结果:
Beijing,China
City: Beijing State: China
San Francisco,the United States
City: San Francisco State: the United States
*/
- getline可以输入整行字符串(要包括string头文件),输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号),将分隔符作为getline的第3个参数即可,例如:
vector对象:封装动态数组,元素个数可在运行时改变,支持下标越界检查。
VI. 继承与派生 (Inheritance, Ch 7)
- 继承的基本概念:特殊类继承一般类,实现代码复用 , , 。
- 继承访问控制(公有、私有、保护), 。
- 派生类的构造和析构函数(构造顺序、析构逆序)。
VII. 多态性 (Polymorphism, Ch 8)
A. 运算符重载
- 运算符重载的意义:对已有运算符赋予多重含义,作用于自定义类型 。
- 不能重载的运算符:
.,.*,::,? :。 - 重载为成员函数:参数个数 = 原操作数个数 - 1(左操作数隐式为
this)。 - 重载为非成员函数:参数个数 = 原操作数个数;至少有一个自定义类型参数;可声明为友元访问私有成员 。
- 前置单目运算符(如
++p):重载为成员函数时无形参。 - 后置单目运算符(如
p++):重载为成员函数时,必须有一个int形参用于区分(该形参无实际意义)。 - 流插入
<<和流提取>>运算符:必须重载为非成员函数(通常为友元)以使左操作数为流对象 , 。
1. 双目运算符 B重载后,- 表达式operand1 B operand2 等同于operator B(operand1, operand2)
2. 前置单目运算符 B重载后,表达式 B operand 等同于operator B(operand)
3. 后置单目运算符 ++和–重载后,表达式 operand B 等同于operator B(operand, 0)
- 表达式operand1 B operand2 等同于operator B(operand1, operand2)
B. 虚函数与抽象类
- 虚函数(
virtual):实现运行时多态(动态绑定)的基础。 - 虚函数必须是非静态的成员函数;构造函数不能是虚函数,析构函数可以是虚函数 。
- 虚表(vtable)与虚指针(vptr):多态类有虚表,对象有虚指针指向虚表,实现动态绑定 。
- 虚函数覆盖规则:派生类函数若与基类虚函数满足特定条件(名称、参数、
const限定符等相同),则自动确定为虚函数并覆盖基类函数 。 - 虚析构函数:必须掌握,确保通过基类指针删除派生类对象时,派生类和基类的析构函数都能被调用,防止内存泄漏 , , 。
- 纯虚函数:基类中声明的虚函数,没有具体操作内容,声明格式为
virtual T func() = 0;, 。 - 抽象类:带有==纯虚函数==的类,只能作为基类使用,不能定义抽象类的对象 , 。
- **
override**:显式声明函数覆盖基类的虚函数,有助于编译器发现“未覆盖”错误(如忘记const限定符), , 。 - **
final**:避免类被继承,或基类的虚函数被覆盖 , 。
VIII. 模板与群体数据 (Templates & Containers, Ch 9)
- 函数模板:解决重载函数逻辑一致但类型不同导致的冗余问题,创建通用功能的函数 。
- 函数模板的定义形式:
template <typename T>。 - 模板实例化限制:只有能进行模板中运算的类型,才能作为类型实参(自定义类需重载相应运算符), 。
- 类模板:为类声明一种模式,使数据成员、参数、返回值能取任意类型 , 。
- 类模板的默认模板实参 。
- 类模板的成员函数定义:在类模板以外定义时,需使用
template <模板参数表>和类名<模板参数标识符列表>::函数名的形式 , 。 - 群体概念:由多个数据元素组成的集合体,分为线性群体和非线性群体 。
- 线性群体:元素按位置排列有序,访问方法包括直接访问(数组)、顺序访问(链表)。
- 动态数组类模板:如
Array类,实现运行时可变大小的数组,通常包含动态分配的指针成员T *list, 。 vector对象:用类模板实现的动态数组,提供自动管理和下标越界检查 , 。- 链表:一种动态数据结构,由结点组成,结点在运行时动态生成 。
- 单链表结点:包含数据域和指向下一个结点的指针(
next), 。 LinkedList类(链表):包含表头指针front、表尾指针rear以及用于遍历的游标指针prevPtr和currPtr。- 链表的基本操作:生成、插入(前/后)、查找、删除(头/当前)、遍历、清空 , .
- 栈(Stack)和队列(Queue):可以通过链表类实现,队列遵循先入先出(FIFO)原则 , 。
- 常函数重载在链表结点类中的应用:提供普通版本和
const版本(如nextNode()或data()),以保障const对象的数据安全。