面向过程的编程方式
基本数据类型:整型(int, long),浮点型(float, double),字符型(char)
通过算术运算符、关系运算符、逻辑运算符、位运算符组成各种表达式
条件语句(if-else, switch-case-default),循环语句(for, while, do-while)
指针指向一个内存地址,数组就是指针,可以使用指针实现动态分配内存
可以将一段代码封装称一个函数,提高代码的可读性与可维护性
使用struct创建结构体,将不同的数据储存在一起
使用union创建联合,一个联合中只能存储一个数据
使用typedef可以给数据类型起别名,使用typeid可以获得数据类型的名称
C++拓展自C语言,强大而又复杂的语言
仅有True和false两种取值,占内存中的1字节
引用可以理解为别名,等效于*p,其中p是指向目标的指针
引用在声明时必须初始化,引用一旦初始化,就不能再指代其他对象。声明引用不消耗内存,但是指针需要消耗内存。
常引用可以使用常量或者表达式初始化,而普通引用只能使用对象名初始化
不能通过常引用对对象进行修改
C++中使用namespace定义不同的名字空间,各个名字空间中的变量独立,与空间中名字相同的全局变量将会被覆盖
:: 为作用域运算符,使用namespace::variable来指定使用的变量,使用::variable来指定使用全局变量。
namespace std 为C++标准库的名字空间,使用using namespace std可以在所有的函数前加上std::.
C++中的函数参数可以使用引用,可以实现与指针类似的效果,但是更加清晰
由于引用传递不会创建新的变量,因此效率更高
引用返回比指针返回更加直接,同返回指针,不能返回函数中定义的局部变量
指针同样可以指向函数,由于解引用符*的优先级低于函数运算符(),因此需要加括号,即 (*fp)(x, y)
与多文件编译中static作用一致
内联函数,调用方法不是传递参数,而是直接将函数体代替函数调用语句,用于较为简单的函数,可以节省函数的调用开销。现在的编译器一般会自动优化。
C++中的函数可以指定默认参数值,但是指定默认值的参数必须放在最后,当有多个有默认值的参数时,顺序需要程序员自己权衡。
在使用默认参数的函数时,需要保证不会出现歧义,即重载函数与默认参数的函数可以使用同样的方式调用。这样的情况编译器一般会报错。
面向对象的基本特征:抽象、继承
面向对象基本特征:多态
面向对象的基本特征:多态
面向对象的基本特征:多态
抽象类
面向对象的基本特征:继承
多继承中消除歧义
C++中可以使用关键词new申请空间,使用delete删除new申请的空间
C++中可以使用关键词new[]申请一系列的空间,使用delete[]删除new[]申请的空间
int* p = new int[15];
delete[] p;
对于功能相似、实现相同、数据类型不同的函数,可以使用函数模板实现
template
类型 函数模板名(参数列表){...}
在函数体内,可以使用T1,T2作为数据类型名使用
如果可以通过参数列表直接推断类型T1,T2,,则可以直接使用一般函数的调用方式
函数名(int , int)
如果不能通过参数列表直接推断类型或者需要指定,则可以使用尖括号
函数名(int, int)
函数模板也可以重载,但是函数模板的重载可能会发生歧义,当编译器发现函数模板重载存在歧义时,将会报错
当使用template<>时,相当于告诉编译器,但函数模板的参数数据类型与该函数一致时,调用该函数,或者说,相当于告诉编译器该函数是专门为该函数所对应的数据类型编写的
类模板使用同样的语法
在定义类模板时,相当于定义了一个容器。在使用抽象数据类型时,可以使用类模板进行定义。
使用template<>可以对类模板进行专门化定义,下面是一个例子,定义了string的专门类模板
template
class Stack{...}
//类模板专门化
template<>
class Stack{...}
使用try-catch命令捕获程序中出现的异常,一个try可以和多个catch命令联系
在函数后使用throw可以声明异常,当需要函数发生异常时可以使用throw关键词抛出
C++文件包含命令为#include<库>,需要注意的是C++标准库不需要写后缀名
包含自己同目录下的文件一般使用#include "文件.h/.cpp"
#ifndef HEAD_H
#define HEAD_H
代码
#endif
常使用上述代码来避免重复编译的问题,规范为文件名大写,点换成下划线
C++代码中类的声明与定义一般在不同的文件中,声明在.h文件中,定义在.cpp文件中,使用时需要将多个文件一同编译,常用的工具有cmake
extern:用在一个程序有多个编译单元时,表名该变量定义在别的编译单元中进行了编译
static:静态的对象只能在自己的编译单元中使用,即使其他单元中使用了extern也不能使用。
类使用class关键词定义,一个类就相当于包含函数、数据的一种数据类型,此外还具有信息隐藏性
类中使用public、protected、private定义类成员的类型,在不显示指定的情况下默认为private的
公有的类成员,可以被所有人访问
受保护的类成员,可以被自己与继承自己的所有子类访问
私有的类成员,只有自己可以访问
需要注意的是,该访问权限是针对类的,而不是对象,也就是说,同一个类的不同对象可以互相访问类成员,包括私有成员
类中所有的函数均有一个隐含的参数this,为指向该类的指针
类体中不能直接对变量进行赋值?实际上可以,该值将会被视为初始值,但不建议这样做,应当在构造函数中对类成员变量进行初始化(书上写不能,但实际上可以)
类体中定义的函数默认为inline的,所以复杂的函数应当定义在类外
构造函数:类名(参数表) : a(a), b(b){};
构造函数在实例化类时调用,使用成员初始化列表可以在成员较多时使可读性更高
构造函数可以被重载,一个类可以有多个构造函数;构造函数也可以有默认参数值
复制构造函数:类名(const &类名 参数名);
复制构造函数允许使用另一个类对象创建一个类对象
显式使用另一个对象取初始化新的对象
在函数参数 值传递中
函数返回值中
当构造函数只有一个参数,或者从第二个参数开始都带有默认值时(第一个参数可以带默认值),该构造函数就可以将第一个参数类型的变量自动转换成当前类型
重载运算符():当前类型 -> 其他类型
析构函数在删除类时调用,一般为作用域结束时、delete后调用
析构函数不能有参数,不能重载
如果定义的类中使用了new申请了空间,就必须在析构函数中使用delete删除
省略时编译器会自动创建一个空的构造函数或者析构函数
需要注意的是,当显示的指定时构造函数时,编译器就不会自动创建空的构造函数,这意味着当只存在显示指定的有参数的构造函数时,就不能使用无参数的构造函数
调用构造函数的顺序:谁先定义谁先调用
调用析构函数的顺序:与构造的顺序相反,谁后构造谁先调用
Cpoint& Cpoint::operator=(const CPoint& ref){
X = ref.X;
return *this;
}
赋值成员函数即重载运算符=
为了保证不修改被引用的对象,使用const修饰参数
返回值为当前变量的引用
每个类都需要有一个赋值成员函数,若没有定义,则会自动生成
静态的数据成员在多个对象中只有一份数据,当数据修改时,所有对象中的数据都被修改
静态的数据成员必须在类外初始化(包括const static)
静态的数据成员也受private与protected约束,在类外不能访问
静态函数只能访问类的静态成员,不能访问非静态成员
静态函数没有this指针
A::count = 1;
A::add_count(1);
在类中可以使用const声明常量,这样声明的变量不能被改变,只能通过构造函数成员初始化列表进行赋值
可以使用const修饰函数:void print(int a) const;
这样的常成员函数只能使用数据成员,而不能修改任何数据成员。
在const函数中只能使用const成员,不能使用非const成员
const是函数的一部分,不论是在类外定义还是类内定义,都不能省略
使用new创建的对象,只能使用delete删除,与生存域等无关
使用其他类作为参数时,构造函数中的成员初始化列表可以使用其他类的构造函数进行初始化
A(B b) : B(b){};
A(int i, int j) : B(i, j){};
友元使用friend作为关键词,顾名思义就是朋友的意思,友元可以访问该类的所有数据包括private与protected
友元函数需要将外部函数在类中声明为友元,可以在外部定义,也可以直接在友元声明处定义。后面的例子是两种创建方式,均可以直接通过函数名调用
class CurClass{
int private_int = 1;
public:
friend void function(CurClass& cur_class){
cout << cur_class.private_int << endl;
}
};
class CurClass{
int private_int = 1; // 这样写不规范
public:
friend void function(CurClass& cur_class);
}
void function(CurClass& cur_class){
cout << cur_class.private_int << endl;
}
可以在一个类中将另一个类声明为友元类,该友元类可以访问该类中的所有变量与函数
对象是类的实例化,是使用类作为数据类型的变量
要访问对象中的数据成员,使用运算符 .
对于访问指向对象的指针,使用运算符->
一旦一个对象声明为了const的,那么该对象只能访问公有的const成员
int A::* ptr = &A::a;
A obj;
obj.*ptr = 8;
使用 ->* 来使用 指向对象的指针 访问 指向成员的指针 指向的成员
A* p = &obj;
p->*ptr = 8;
通过关键词public、protected、private实现对类成员的访问限制,达到封装的目的。
通过将定义与声明分开的方法,达到隐藏程序代码的目的
继承是复用代码的一种形式,允许程序员在现有类的基础上创建新的类,其中原有的类叫做基类(父类),新的类叫做派生类(父类)
派生类几乎继承了基类中的所有成员(构造函数、析构函数、赋值函数除外),并增加了新的成员。
子类继承于单一父类
子类继承于多个父类
如果一个类有多个基类,在这些基类中存在名字相同的成员,这是就不能直接使用名字访问这些成员,而是要通过 基类::成员 的方式进行访问
一个类不能多次直接声明为一个派生类的基类,但是可以不止一次的声明为一个派生类的间接基类,这种情况下将会造成多份基类数据
在声明派生类时,在继承基类方式前可以加virtual声明为虚继承
当一个类被声明为虚基类时,它的所有子类(包括间接子类)都需要显式的使用虚基类的构造函数,且虚基类的构造函数仅调用这一次。
class 子类 : public 父类1, private 父类2...{
...
};
构造函数不会继承,子类的构造函数不仅要初始化子类,还有初始化继承的父类,这点可以通过成员初始化列表实现
class 子类 : public 父类1, private父类2{
public:
子类(参数):父类1(参数), 父类2(参数){};
}
子类的构造函数的成员初始化列表的执行顺序不由书写顺序决定,而是先执行基类的构造函数
析构函数不会继承,因此子类的析构函数必须重写
在派生类的定义中,需要指定继承方式,关键词为public、private、protected
基类中的public与protected被继承,且属性不变,private成员虽然被继承,但是却不能在子类中访问它们,只能通过基类来访问
以下情况可以访问继承的private成员:
基类中有private成员c,并有public的方法get_c()
在子类中,可以通过调用继承的get_c方法来获得c的值
基类中的public与protected被继承,但是变为protected成员
基类中的public与protected被继承,但是变为private成员
若通过子类调用基类中的方法(子类没有),则会从基类的方法中调用相应的方法
若子类中有与基类中相同名字的函数,则会屏蔽所有基类中的函数,不论参数列表是否一致
如果要在子类中调用基类中被屏蔽的方法,需要使用 :: 运算符
由于派生类得到了基类几乎所有的成员,所有可以在需要基类的地方使用派生类,这是将自动将派生类转换为基类
用派生类对象赋值或初始化基类对象
用派生类对象初始化基类引用
将派生类对象的地址赋给指向基类的指针
在向上类型转型中,派生类对象中的新增成员被舍弃
运算符重载是函数重载的一种特殊方式,运算符也是一种函数
运算符重载的声明:函数返回类型 operator 运算符(形参列表);
. 成员访问运算符
.* 成员指针访问运算符
:: 作用域运算符
sizeof
typeid
?: 三目条件运算符
二元运算符(以*为例)需要一个参数,其执行结果相当于
A.operator*(B) -> A * B
建议重载为类友元函数
一元运算符
必须重载为类成员函数
类名::operator Type();
使用该重载方法可以将当前对象转换为其他对象
类名& 类名::operator += (const 类名& c);
需要返回结果的引用
建议重载为类成员函数
const 类名& 类名::operator ++(){
++函数体
return *this;
}
前置对对象进行++操作后直接返回引用即可
其中const意味着返回值不能作为左值
const 类名 类名::operator ++(int){
类名 temp(*this);
++函数体
return temp;
}
其中的int只是为了将前置与后置区分开,没有使用
后置需要先保存++前的值,在进行++操作,最后返回++前的值
必须重载为友元函数
二元运算符需要两个参数,其执行结果相当于
operator*(A, B) -> A * B
建议重载为类友元函数
不建议重载为类友元运算符
operator-(A) -> -A
特殊类型转换运算符必须使用非静态的类成员,因此不能使用重载为友元函数的形式
friend 类名& operator +=(类名& a, const 类名& b);
a为计算的结果值,应当返回a
不建议重载为类友元函数
friend const 类名& operator++(类名& a){
++函数体
return a;
}
friend const 类名 operator++(类名& a, int){
类名 temp = a;
++函数体
return temp;
}
friend istream& operator>>(istream& is, 类名& a){
is >> 输入格式;
return is;
}
friend ostream& operator<<(ostream& os, const 类名& a){
os << "文本";
return os;
}
都可以用,建议重载为类友元函数
都可以用,建议重载为类成员函数
只能重载为类成员函数
都可以用,建议重载为类成员函数
都可以用,建议重载为类成员函数
只能重载为类友元函数
在编译、连接阶段将函数根据不同的参数与名字与函数体连接起来
函数调用与函数体的联系在程序运行时才确定
声明:在函数声明前添加关键词virtual
若在基类中一个函数被声明为虚函数,则在所有的派生类该函数也将作为虚函数存在,称为基类虚函数的重写
派生类中的基类虚函数重写一定要是相同的,包括名字、参数、const等(不包括返回值),否则不能称为基类虚函数的重写
public继承
虚函数
通过指针(或引用)调用
虚函数一定是非静态成员函数
使用作用域运算符 :: 时,虚机制不在起作用
在派生类中重写虚函数时,如果原函数有默认形参值,就不要再定义新的形参值,因为形参默认值是静态绑定的,只能来自基类定义
可以在子类中不重写基类的虚函数,这么做则将基类中的虚函数作为子类的虚函数
只用虚函数可以动态绑定,如果要实现动态绑定,就需要将基类中的相应函数声明为虚函数
构造函数不能是虚函数,但是析构函数可以是虚函数
对于使用new创建的由基类指针指向的子类,会调用基类与子类的构造函数,如果使用delete删除,则只会调用基类中的析构函数
将基类中的析构函数声明为virtual的,则基类与子类的析构函数都会被调用
纯虚函数没有函数体,在类中仅作为基类的接口,由子类进行实现。抽象类只能作为基类使用,不能实例化
virtual 返回类型 函数名(参数列表) = 0;
如果一个类中有一个纯虚函数,则该类是一个抽象类
如果派生类中给出了所有纯虚函数的实现,则该类不在是抽象类,否则该类仍然是抽象类
子类中的纯虚函数必须被重写,否则该子类仍然是抽象类,不能被实例化
C++通过纯虚函数来实现面向对象中的抽象