C++ 复习思维导图
引言
- 如何在C++中调用C的函数
- 编译->链接
- 连接错误:缺了?多了?
- Missing:C与C++的修辞功能不同
- extern “C” void c_fun()
- myc.h 中如何声明,使得include不会出错?
#ifdef __cplusplus //预定义的宏,用C++兼容 extern "C" { ... } #endif
- Library
- dynamic
.dll
动态链接库 - static
.lib
参与原程序的生成,被.exe
包含 - 构建库
- 没有
main()
- 编译生成
.lib
- 没有
- 使用库
- 拷贝到工程文件夹下
- 工程配置倒入库
#pragma comment(lib,"testlib.lib")
- dynamic
万物皆对象
对其成员/属性(函数、变量)进行保护
struct 默认公有
class 默认私有
对象就是一段连续的内容
class Student {
private:
int age;
public:
void init();
}
Student s1; //8个字节的前4个是age
int *p = (int *)&s1;
*p = 30;
封装
分治————访问控制
- 运行内存 sizeof() (函数不占)
- 加载内存
//按照大字节对齐来分配内存
int i,j;
char c1,c2; //12
double d;
int i;
char c; // 24
三无
- 语言无关
- 平台无关
- 应用无关
并发的解决思路
- 并转串:根据用户量构建缓冲区,服务器处理(实时要求低)
- 多机:入口机分配任务给其他处理机
构造,析构,拷贝
- 理想的分工方式:分层(库程序员,中端~,…)
- 实际上:按模块,有重复
- 正太:自然
- 幂律:人为
- 声明时隐式构造:
Student s(10,"liu");
Student s;//没有参数,则不要加括号,调用的是无参数构造
Student s();//否则为函数声明
Student(); //人为不定义时默认构造器,一旦定义就不再存在:声明时不得不传参数
- 默认参数构造:
Test(int i,int j=0)
性能低,不建议 - 程序运行空间:
- 静态: 常量,代码区
- Runtime: global
- stack : 局部变量————不同寻址(快),但生命周期短
- heap: 动态分配空间————new
- handle: (某类中)
- Student s;//必要组成部分 value
- Student *s; //不必要 handle
- int 的构造函数:
j =new int(aj); int i(10);
- 析构:
- 对象消失时(对象所在函数调用完毕)自动调用
- 无条件(参数),唯一
- 没有显示析构时,编译系统会自动生成缺省的析构
- 定义类析构,则先调用自定义再调用合成(无操作)
~Test();
- 只要构造有new,一定有析构,释放一切空间
- 数据成员:属性/通信(函数交换信息)
- D内存泄漏:handle丢失,但对象未被释放
- A释放出错
Test(int aa){ i=aa; //如果只初始化i,则j为野指针,释放会出错 j=NULL;//需要这一步!!! }
引用 reference(安全的指针)
```
int i=10;
int& r=i; //引用必初始化:r是i的引用
r++; //i++;
r=j; //i=j;一旦绑定用不分离
```
- 函数传递引用(地址)
- pointer:ugly but clear
- reference: simple but arduous
克隆构造 copy constructor
Test t1 =new Test(1,2);
Test t2(t1);
- B浅拷贝 bitwise copy
- 完全一样
- 类中默认构造 memcpy
t2先死,delete 2,但t1死的时候也会delete,C报错 - 任意指针不能直接free p,应该
if(p!=NULL){free p;p=NULL}
- 深拷贝 logical copy
Test(Test& t){
i = t.i;
j=new int(*t.j);
}
- E返回局部变量地址
static
static对象 | 可见域 | 生命周期 | 性质 |
---|---|---|---|
局部变量 | 函数内部 | 同全局变量(main后释放) | 定义一次后不再构造,直到程序结束死亡 |
全局函数 | 仅本文件可见 | static void fun() | |
数据成员 | 本类所有对象 | 初始化不由构造函数,显式在类外似全局变量一次初始化Test::i=0; |
|
函数成员 | 本类所有对象 | 函数调用不由创建对象;为了调用static数据成员;调用了非static成员则不能加static |
-
程序通讯
- 函数调用
- 数据成员
- static数据成员
- 全局变量
-
全局变量定义在源文件中,头文件两次include相当于重定义
-
extern(外连接)会在所有源文件查找,多个则重定义。与static相反
-
名空间 namespace
- 解决大型工程重名问题
namespace T1{ class ABC{ } } using namespace T1; or T1::ABC a;
-
传值为传拷贝,会调用拷贝构造
-
Never pass by value(大对象)
- built-in type(原生)
- defined type
- self-defined type
-
void dun(const Test *t) 传递指针常量,不可更改
- 只读
- 规定只要没有const就要写
-
inline 内联函数
- 函数定义或声明前加inline(定义必加)
- 用来定义一个类的~,替代C中表达式的宏定义:高效、审查、私有
- 用在函数内容简单时候
- C的宏定义
- 使用预处理器实现,没有参数压栈,代码生成,效率很高。
- 但没有C++编译器审查,返回值不能强制转换
- 涉及到私有,不能用它实现
-
常量函数————常量对象调用
int getI() const{return i;}
- 前提:不能对该对象修改
setI(i)
-
单件模式:
- 希望某个类只有一个实例可以被访问
- 全局对象:
- 程序质量降低
- 不能实例化多次
- 不能保证只有一个实例
class Scheduler { schedule(){} static Scheduler *self = NULL; public: static void get_instance(){ if(self==NULL) self = new Schedule(); return self; } }
运算符重载:
- 非必需
- 与认知相符(如字符串相加)
a.save(100)
–> a=a+100 (Account a)
Account operator+(int money){
balance+=money;
return *this;
}
- 重载运算符:
+
-
*
/
[
]
<<
new
- new/delete
- delete = destructor + free
- new = malloc +constructor
- malloc/free
- 预先不知道申请大小,来一个分配一个
- 动态放堆区:堆>>栈
- 人为控制生命周期:
AutoAC ac;//栈区,无法控制 new AutoAC();//析构delete Memory *p = new Memory; Memory *p = (*Memory)malloc(sizeof(Memory));
继承
- 内涵:组合
- 外延:继承
- 两个类的共性:抽象为同类
class Teacher: public Borrower {
void borrow() {
Borrower::borrow(); //完美继承,肯定父类
cout<<"5 books"<<endl;
}
}
代码重用 reuse
- 子类不能使用父类的私有对象。protected 可以
- 父类private:子类削弱了父类
- 构造:先父后子(默认构造)
- 析构:先子后父
Cat():Pet(2,"cat")
{
cout<<"cat"<<endl;//括号外调用
}
Cat(age,name,type):Pet(age,name),type(type){}//面向对象构造
创建Car,一定会调用Engine()
Car():e(1){
cout<<"car"<<endl;
}
- 按照声明顺序执行构造:
Test(int a):j(a),i(j){} : i=j野值;j=a;
- 私有继承
class Cat:(private) Pet //削弱了父类接口,方法变为私有
多继承
class Base1 {
void f();
}
class Base2 {
void h();
void f();
}
- ambiguous
- 用组合代替多继承
多态
子类需要承认共性,再加入特性
多态性
- 靠虚表实现————有损性能
- upcasting 向上类型转换
pet.speak();
<–cat - 子类型不能削弱父类接口
- binding 绑定
- 将标识符(函数、变量)转换为地址的过程
- early binding(static)
- 编译器/链接器能够直接关联
- 所有功能有唯一的地址
- later binding(runtime,dynamic)
- 只有在运行时才知道该调用哪个
- 使用函数指针(间接函数调用)
int add(int x,int y){return x+y;} int main(){ int (*pFcn)(int,int)=add; cout<<pFcn(5,3)<<endl; }
- 效率低下
- 虚函数
Pet:virtual void speak();//自动继承
Needle(Pet & pet);
Needle(Pet pet);//调用了拷贝构造,指向Pet,不会调用子类的speak
- 虚函数表(v-table)
- 虚指针(v-ptr):在类中占用4个字节
- 每个使用虚函数的类都有虚拟表
- 在编译时形成静态数组,存放函数指针,指向该类可访问的最派生函数
- 虚指针由构造函数隐式完成
纯虚函数(仅适用于抽象类:不能创建实例)
virtual void speak() =0
世界上存在Cat,Dog 不存在Pet
子类不去实现则仍为抽象类
意义:
- 传的是引用,而不是实例
- 纲领
- 顶层抽象设计,规定所有子类的行为
- 可以有函数体,子类调用
Interface
class Machine()
class Airplane() : public Machine, public **FlyObject** void fly();
class Bird(): public Animal, public **FlyObject** void fly();
class Radar() void scan(???#whatcanfly **FlyObject& flyer**);
- 重载一个scan?
- class FlyObject() virtual void fly()=0;
- 串联了由于行为共性而本不相关的类型
被否定的多重继承???只有虚函数,但是没有数据成员。接口是一个抽象的小类
构造函数可能是虚函数?不可能,构造函数恒无二意性,与多态无关
析构函数是虚函数?一般是
void fun(Base* base) …delete(p)
class Base() virtual ~Base();
- 保证继承的所有子类析构于其类型
- 降低了性能:假如有一个虚函数(已经存在虚指针),其他的能虚就虚
- static与行为正确性无关,与虚无关
template——reuse
class Stack{
int pool[100];
int top;
void push(int i);
int pop();
}
如何建一个double的栈?万能的栈?
template <class T> class Stack{
T pool[100];
int top;
void push(T i);
T pop();
}
- 模版类
- T为类型参数
- 行为上毫无区别,只是类型区别
- 容器最适用使用模版——-STL
#include<vector>
vector<int> vi;
for(i=1;i<=10000;i++) vi.push_back(i);
- 万能容器
- 动态增长
- 重载了运算符
<<
list
vector
- 工程:价值导向,只有0和1。在性能问题出来之前不考虑
- 科学:真理导向,向前曲折推进
Iterator——所有STL类的内部类
vector<int>::iterator it = vi.begin();//*it=0
while(it !=vi.end()){
cout<<*it<<endl;
it++;
}
- it伪装成指针,重载了
++
--
*
Factory模式
Sample sample=new Sample();
可是,实际情况是,通常我们都要在创建sample实例时做点初始化的工作,比如赋值 查询数据库等。
首先,我们想到的是,可以使用Sample的构造函数,这样生成实例就写成:
Sample sample=new Sample(参数);
- 如果初始化是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的
- 创建实例所需要的大量初始化工作从Sample的构造函数中分离出去
需要将Sample抽象成一个接口.
Sample mysample=new MySample();
Sample hissample=new HisSample();
随着项目的深入,Sample可能还会"生出很多儿子出来", 你会建立一个专门生产Sample实例的工厂:
public class Factory{
public static ISample creator(int which){
if (which==1)
return new SampleA();
else if (which==2)
return new SampleB();
}
}
那么在你的程序中,如果要创建Sample的实列时候可以使用
Sample sampleA=Factory.creator(1);
这样,在整个就不涉及到Sample的具体的实现类,达到封装效果,也就减少错误修改的机会
graph LR
产品接口---接口实现类
接口实现类---工厂
工厂-->生产产品接口的具体实例
Observer模式
详见深入浅出设计模式——观察者模式(Observer Pattern)
问题探讨
案例1
- 代码
#include<iostream>
#include<cstring>
using namespace std;
class Person {
public:
virtual void print(){
cout<<"I am person!\n";
}
};
class Student:public Person {
public:
virtual void print(){
cout<<"I am student!\n";
}
};
void test(Person& p){
p.print();
}
int main(){
Person a; Student b;
Person *xa =&a; Student *xb = &b;
memcpy(xa,xb,4);
a.print();
test(a);
(*xa).print();
return 0;
}
- 运行结果
I am person!
I am student!
I am student!
- 类图
memcpy
之前
memcpy
之后
- 原因:
a.print();
采用的是静态链接,实际上使用的是memcpy
之前的print方式test(a);
(*xa).print();
为指针或引用,采用动态链接,使用的是memcpy
之后的print方式
案例2
- 代码
class Base
{
public:
virtual void function1() {};
virtual void function2() {};
};
class D1: public Base
{
public:
virtual void function1() {};
};
class D2: public Base
{
public:
virtual void function2() {};
};
- 类图
虽然这个图有点疯狂,但它非常简单:
- 编译器会添加一个隐藏指针,指向使用虚函数的最基类Base
- D1,D2从Base继承*__vptr
- 每个类中的* __ vptr指向该类的虚拟表。
- 虚拟表中的条目指向允许调用该类的函数对象的最派生版本。
评论区