多态(virtual)
泛型(template)
实践
-
既可以在函数声明中,也可以在函数定义中声明缺省参数,但不能既在函数声明中又在函数定义中同时声明缺省参数。因此,将定义或声明中的任一个缺省参数删除即可。
翁凯C++
2 什么是面向对象
Objects = Attributes + Services
data是被operation包在里面的
面向过程与面向对象
one example
3 面向对象基本原理
OOP Characteristics
- What to do not how to do
An object has an interface
- The interface is the way it receives messages
- It is define in the class the objecvt belong to
interface:
- Communication
- Protection
Encapsulation(封装)
- bundle data and methods dealing with these data together in an object
- Hide the details of the data and the action
- Restrict only access to the publicized methods
4 自动售票机例子
5 头文件
The header file
.cpp
经过编译预处理→.ii
,经过编译→.s
,经过汇编→.o
,经过ld链接→.out
Declarations vs. Definitions
如果在.h文件中variables不加
extern
关键字,则是对variables的定义,在多个.cpp链接该.h文件时会出现ld:duplicate symbol
的错误加上
extern
关键字才是声明Declaration#include:
Standard header file structure:
上面这一章讲的内容的一个相关概括性文章
6 时钟的例子
设计对象的时候划分的粒度
抽象Abstract
7 成员变量
Field, parameters, local variables
成员变量,参数,本地变量
- 成员变量的作用域是类的作用域,在类的函数中使用
- 本地变量只能在其所在的函数中使用
成员变量在类的每一个对象中
parameters = local variables
8 成员变量的秘密
函数是属于类的,不是属于对象的
this:hedden parameter
this指针指向对象自身
9 构造和析构
constructor function构造函数
名字和类的名字相同
没有返回类型
在对象被创建的时候自动被调用
destructor function析构函数
- 在对象生命周期scope结束的时候自动调用
10 对象初始化
Storage Allocation
空间在进了大括号之后就会分配
构造函数则是到定义的那一行才会调用
有构造函数初始化的时候就要用构造函数,不能用括号(下图x2和y1初始化时的区别)
11 new&delete
Dynamic memory allocation
new运算符的返回值是地址
new时会调用构造函数
delete时会调用析构函数
delete带方括号是用来删除一段空间,例如数组
1
2delete p;
delete[] p;Tips for new and delete
new了之后记得delete,避免内存泄露
12 访问限制
private-public
C++ access control:public<protected<private
public【能给所有人操作的数据或方法】
private:只有类的成员函数可以访问,但是同一个类的对象之间可以访问private成员【数据一般都设置为private】
因为C++的object oriented特性只是在编译的时候做的,只在编译器中检查,运行时不检查
protected:private在派生类中不可以被访问,而protected可以【里面一般是留给子类来操作数据的接口】
friend
class vs. struct
13 初始化列表
对于类A的成员变量p,初始化列表为:
1 | A():p(value){cout<<"function body"<<endl;} |
initialization vs. assignment
用initializer list进行初始化做的是initialization
在constructor中赋值做的是先initialization(此时没给参数,相当于用的是default constructor;若类A有个类成员变量是一个类B的对象,且类B无default constructor,则会报错),再assignment
【default constructor】指的是没有参数的构造函数
总之,最好用initializer list初始化,而不要在constructor的function body中做赋值
另外,父类的初始化也要放在初始化列表中
14 对象组合
Composition
Fully和By reference是两种内存模型
前者是类成员变量,后者是类成员变量指针
一个对象组合的例子:
声明:
构造函数写法:
initialization list
15 继承
继承和组合不同,但都是软件重用的方式
继承inheritance:
一个类的interface指的是它对外公开的部分,即被public的member data和member function
一些名词描述:
一个例子:
实例代码:
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
42
43
using namespace std;
class A
{
public:
A(int ii) : i(ii) { cout << "A:A()" << endl; }
A() { cout << "A:A() with no arg" << endl; }
//想让编译通过就得给一个无参的default constructor
//在B b;时会调用A的构造函数,因为B没写构造函数
//或者给B添加一个构造函数,在其初始化列表中给A中的i赋值
~A() { cout << "A:~A()" << endl; }
void print() { cout << "i:" << i << endl; }
protected:
void set(int ii) { i = ii; }
private:
int i;
};
class B : public A
{
public:
/*
B() : A(10) { cout << "B:B()" << endl; }
要在初始化列表中初始化父类A的成员变量i(即调用A的构造函数)
有上面B的构造函数,就不会调用A的default constructor
*/
void f()
{
set(100);
print();
}
};
int main()
{
B b;
b.print();
b.f();
return 0;
}
16 父类子类的关系
名字相同,但参数表不同,是对函数的重载(overload)
上述的写法可以避免code duplication
name hiding(OOP语言中只有C++有这种特性)
对C++来说,父类A有一个overloaded函数(假设为
func(int i)
和func()
),子类B有一个同名的函数,假设为func()
,则在子类中func()
会覆盖掉父类继承来的func()
想调用父类的那个函数需要
b.A::func(777);
17 函数重载和默认参数
返回值不同的话不能构成overload
默认参数:void stash(int size, int intiQuility = 0);
default argument一定要从最右侧的参数往左开始default
default argument只能在函数声明里写,不能在函数定义里写
因为这是再编译的时候检查的,这个函数还是需要俩参数,但是有函数声明中对参数默认值的声明,相当于在编译阶说明了这个函数的其中一个参数的值。并非生来如此,是后来说明的
18 内联函数
Overhead for a function call(函数调用时的额外开销)
堆栈push和pop等开销
inline函数可以不需要上述操作,像预处理宏一样
把函数代码嵌入到调用的位置,但仍然保证函数的独立性,如函数独立的命名空间等等
inline function
Repeat inline keyword at declaration and definition
inline函数的body只用放在header file,即.h文件中
inline函数的定义相当于只是声明
inline may not inline:compiler may not honor your request to make a function inline
成员函数在类class声明的时候,就给出函数的body,则这些函数都是inline的
inline or not?
19 const
Pointers and const:
对象是const:不能通过该指针来修改对象(对象本身不一定是const)
指针是const:指针本身所指的位置不能修改
区分的理由是:const写在星号*的前面还是后面
const在星号前面,**const p**,const修饰的是p,i.e.对象是const
const在星号后面,* const p,const修饰的是p,i.e.指针是const
一些例子:
要把一个大的对象作为参数传进函数
为了避免拷贝等操作耗时耗力,可以用指针
为了避免因指针导致对象被修改,可以用const
对于C++中的类和对象:
在对象定义/声明前面加const意味着这个对象中的所有变量都不能被修改
在类的成员函数声明/定义时加const意味着这个函数不能修改类的成员变量
因为
int get_value() const { return value; }
中const意味着*this是const的
20 不可修饰的对象
接19 const
下面的这个函数,加了const后实际上相当于overload
运行结果是”f() const”
两个函数相当于:
void f(A* this) { cout << "f() << endl; "}
void f(const A* this) { cout << "f() << endl; "}
即参数不同
如果类中有个成员变量是const的,则需要在initializer list中初始化
21 引用
引用需要在定义的时候给定初始值
除了参数表,和类的成员变量
函数的参数:引用和值传递不能重载,因为调用形式相同
引用不能再指向别的变量
Restrictions:
离变量最近的符号决定了变量的基本类型
22 向上造型
子类的对象具有父类的对象的所有东西,所以一个子类的对象可以当做一个父类的对象使用
Upcasting:
Upcasting examples:
上述例子中,**
ep->print(cout);
调用的是Employee的print,即父类的print**函数调的是父类的函数,而由于父类有的数据子类也一定会有,所以Upcasting不会出问题
23 多态性
例子:A drawing program
只要祖先中某个函数是virtual的,其子类继承的该函数都永远是virtual的
virtual限制的函数,有动态绑定的特性,在调用该函数时【需要通过对象的指针或引用来调用】,就调用所指对象所属的类型的对应函数
(需要用指针或引用来调用)才有动态绑定,不然没有动态绑定
称之为Polymorphism
24 多态的实现
书接上文,讲如何实现多态
有virtual的类的对象,内存开头都有一个隐藏的vptr指针,指向一张叫做vtable的表
析构也要是virtual的:
调父类的func()
==override还没太懂==
25 引用再研究
类的成员变量是引用类型时,必须在初始化列表中初始化
引用作为返回值时,不能返回函数内的本地变量,只能返回全局变量
返回函数内的本地变量能过编译(有warning),但是会出现
segment fault
除此之外,引用作为返回值后函数可以作为左值:
传一个很大的参数进函数:使用const reference,如
const int& arg
为了节省空间,不能传递值,可以传指针;
为了避免函数修改外面调用者的变量的值,可以传const *pointer;
为了更好的可读性,可以用reference
函数返回类型是自己定义的类,则该函数也可以作为左值
26 拷贝构造
一个对象初始化的时候用另外一个对象初始化,调用的是拷贝构造函数
如果没有自己定义拷贝构造,C++会提供default copy constructor
拷贝构造函数的写法应该是:
A(const A& a){...}
,即参数应该是引用参数如果是个对象,则会造成对拷贝构造函数的递归调用,编译器会报error
写一个类 必备的三个函数:
default constructor
virtual destructor
copy constructor
如果不希望你写的类被拷贝,就写个private的拷贝构造
“双杀”:默认情况,编译器以“位拷贝”的方式自动生成缺省的构造函数和赋值函数。若类中有指针变量,那么缺省的构造函数会出现错误。
因为这个指针变量所指的空间析构多次
28 静态对象
static: two basic meanings
static storage: allocate once at a fixed address
visibility of a name: internal linkage
-
全局静态对象:
全局对象在程序执行 main() 之前已经分配好存储空间 ,在 main() 执行完成后进行释放。
相当于编译器在 main 函数的显示代码之前和之后分别插入了一段代码用来管理全局对象
局部静态对象:
- 局部静态对象是从其所在函数第一次被调用时进行创建,直到整个程序结束才进行销毁;局部静态对象只有在第一次调用时进行初始化操作。
- 利用局部静态对象只会初始化一次、并且不使用不创建的特点,可以用来实现单例模式
29 静态成员
- 注意:static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。
- 静态成员变量在初始化时不能再加 static,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。
- 不能在初始化列表中初始化static成员变量,只能在其定义的时候初始化(即在类外)
"message": `"'int A::i' is a static data member; it can only be initialized at its definition"`
- 可以用
Student::m_total = 10;
来访问,即直接用类的名字来访问
对于成员变量,Hidden的功能已经可以由private/protected来实现
剩下的Persistent功能:Independent of instances
静态成员变量在所有的对象中是保持一致(persistent)的
有时候我们希望在多个对象之间共享数据,对象 a 改变了某份数据后对象 b 可以检测到。共享数据的典型使用场景是计数,以前面的 Student 类为例,如果我们想知道班级中共有多少名学生,就可以设置一份共享的变量,每次创建对象时让该变量加 1。
1 |
|
- 静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 和静态成员变量类似,静态成员函数在声明时要加 static,在定义时不能加 static。静态成员函数可以通过类来调用(一般都是这样做),也可以通过对象来调用,上例仅仅演示了如何通过类来调用。
- 其与类的对象存在与否无关,在对象不存在的时候就可以被调用
30 运算符重载-基本规则
允许重载的运算符
不允许重载的运算符
Restriction限制:
operator关键字+重载的运算符
const String String::operator + (const String& that);
const String operator + (const String& l, const String& r);
作为member function,不能做z=3+y
作为global function,能做z=3+y
31 运算符重载-原型
本章就看上面的记录即可
【是否返回引用】操作符对receiver自己做了修改,则返回引用(如++,–,=);若返回了一个新的对象,则不返回引用,返回一个对象(如+,-,*,/)
【是否返回const】返回值能不能做左值,能做左值的返回类型不带const(如=,[]),不能做左值的返回类型要带const(如+,a+b=6错误)
【参数一定引用】重载一定对某个对象进行重载,所以传进去的参数一定是引用类型的。
【参数是否const】参数能不能加const(member function对receiver自身的const加在函数原型尾部),修改算子的不能加const(如++,–,=),不修改算子的参数要加const,this加const(如+,-,*,/)
根据结合性,所有的关系运算符均为左结合
所以对于关系运算符的重载,如bool operator<(const node &a) const
,表达的实际上是**this<a
**
32 运算符重载-赋值
本章同样看上面的记录
讲的是运算符重载中,赋值运算符重载这个特殊的例子
默认的赋值运算符:
- the compiler will automatically create a type::oprator=(type) if you don’t make one
- 它做的是memberwise assignment,即所有成员变量直接赋值
赋值运算符重载的标准写法:
1
2
3
4
5
6
7T& T::operator=(const T& rhs){
//check for self assignment
if(this != &rhs){//如果是自己赋值给自己,可能在自己被赋值之前被释放,比如new到的东西
//perform assignment
}
return *this;
}//this checks address vs. check value (*this != rhs)
33 运算符重载-类型转换
compilers perform implicit conversions using:
single-argument constructor
即先调用单个参数的构造函数将参数构造为目标参数类型的对象,再调用目标函数,上面记录里有相关例子
要阻止隐式调用构造函数来进行类型转换,可以在构造函数前面加上关键字
explicit
implicit type conversion operators
operator name is any type descriptor
操作符的名字是某一个类型,如double
return type is same as function name
No return type:没有返回类型
No explicit arguments
没有显式的参数
1 | class Rational{ |
用运算符重载的类型转换还是尽量别用, 因为他是implicit的,不容易理解,还是用专门的例如C++中的to_string()
这种函数比较好
34/35 模板
Template:use types as parameters in class or function definitions
function template; templated function; class template; templated class
例子如下:template生效的是跟在template<class T>
后面的函数or类
1 |
|
如果function template的参数表中没有用到T,则需要加一个尖括号,在尖括号中指出T的类型
1
2
3
4template <class T>
void foo(void){...}
foo<int>();//type T is int
foo<float>();//tyoe T is float
class template
1 | template <class T> |
Pair类模板:
1 |
|
36 异常基本概念
- 本文链接:https://wan-nan.github.io/2022/11/06/C++%E5%AD%A6%E4%B9%A0/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。