其他路径:
微信公众号:程序喵星人
更多资源和视频教程,QQ:1902686547
这篇文章是对C++的知识点做了一些简单的总结,基本包含了所有的C++基础知识点。以下提到的知识点并非深入讲解,只是大概讲解了各个知识点的基本使用。如需要深入了解,可以针对某个知识点去深入学习。
一、C++常用后缀
cpp, .h, cc, cxx, hpp
二、头文件
- 头文件#include
- 标准输入(standard input)与预定义的 istream 对象 cin 对应
- 标准输出(standard output) 与预定义的 ostream 对象 cout 对应
- 标准出错(standard error)与预定义的的 ostream 对象 cerr 对应
例子:用c++写一个简单计算器
Copy
Copy
三、 指针与动态内存分配
静态内存分配(全局变量, 局部变量), 动态内存分配(在 c 中用 malloc 分配的堆空间 free 来释放)c++中用 new 分配堆空间 delete 释放。
Copy
- 整形数: int *p = new int(10) ; 分配空间并且初始化为 10 释放 delete p
- 整形数组:int *arr = new int[10] ; 分配十个连续整数空间 释放 delete []arr
- 字符型:char *p = new char(‘a’); 释放 delete p;
- 字符串:char *arr = new char[100];分配 100 个字符空间 释放 delete []arr;
四、命名空间
为了确保程序中的全局实体的名字不会与某些库中声明的全局实体名冲突,引入了命名空间。
- 避免名称冲突;
- 模块化应用程序;
- 匿名的命名空间可避免产生全局静态变量,创建的 “匿名” 命名空间只能在创建它的文件中访问。
除main函数外所有函数, 变量, 类型。
Copy
函数,变量, 类型 } 例子:
Copy
class01::name — ::所属符号
所属符号::
Copy
} }
使用:
- AAA::BBB::number;
- using namespace AAA::BBB; number;
- using AAA::BBB::number;
相当于全局变量直接使用(只能在本文中使用)
static。
定义:
Copy
匿名空间与static的异同:
static 无法修饰自定义类型;static 产生当前 符号只在当前源文件有效,是因为它修改了符号的Bind属性,使之变为局部的;而匿名空间虽然可以产生相同效果,但是符号还是具有外部链接属性。匿名命名空间内的变量与函数,只在当前源文件内有效;不同源文件的匿名命名空间,可以存在同名符合。static需要在每个变量加上
六、引用
引用:就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
引用的声明方法:类型标识符 &引用名=目标变量名;(别名)
int a = 10;
int &ra = a; (ra 就是 a 的引用 ,也称 a 的别名)
- &在此不是求地址运算符,而是起标识作用。
- 类型标识符是指目标变量的类型。
- 声明引用时,必须同时对其进行初始化。
- 引用声明完毕后,相当于目标变量有两个名称即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。
- 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元,但引用本身是有大小的,一个指针的大小,在64位系统:sizeof(&ra) = 8,32位为4字节,sizeof(ra)=sizeof(a)被引用对象的大小。故:对引用求地址,就是对目标变量求地址。 &ra 与&a 相等。
- 不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个
1.引用作为参数
引用的一个重要作用就是作为函数的参数。以前的 C 语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择
2.常引用
常引用声明方式:const 类型标识符 &引用名 = 目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为 const,达到了引用的安全性。
3.引用作为函数返回值
要以引用返回函数值,则函数定义时要按以下格式:
类型标识符 &函数名 (形参列表及类型说明){ 函数体 }
特点:
- 以引用返回函数值,定义函数时需要在函数名前加&
- 用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
- 不能返回局部变量的引用
七、函数重载
函数重载只能在同一个类中
int open();
int open(const char filename);
int open(const char filename , int flag);
c++中编译程序的是检测函数,通过函数名和参数列表
如果在一个文件中出现同名的函数但参数列表不同,那么这些函数属于重载
函数重载的依据:
八、函数缺省参数(默认参数)
int open(const char filename, int flag=10)
int open(const char filename=”c++”, int flag=10)
int open(const char *filename=”c++”, int flag) 错误
注意: 若给某一参数设置了默认值,那么在参数表中其后(也就是右边)所有的参数都必须也设置默认值
九、类与对象
(1)定义:
Copy
类的特征(属性) 成员变量 类的行为(功能) 成员方法, 函数 }; 注意:当类里面的成员参数函数有默认值时,若需要在外部定义该函数时,不能写默认值,默认值只能在类里面声明的时候写默认值。 例子:
{ month = m ; day = d ; year = y ; }
};
struct和class在C++都能定义类,其区别:
- struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
- 默认的继承访问权限。struct是public的,class是private的。
(2)类成员权限控制
- public 公有 公有段的成员是提供给外部的接口,在外部可以访问
- protected 保护 保护段成员在该类和它的派生类中可见,在类外不能访问(也就是在外部创建对象时不能访问)
- private 私有 私有段成员仅在类中可见(友元函数或者友元类可以访问),在类外不能访问(也就是在外部创建对象时不能访问)
(3)类内/外访问
- 类内访问:在类的成员函数中访问成员(没有任何限制)
- 类外访问: 在类的外部通过类的对象访问类的成员
定义与成员访问:
Copy
{ month = m ; day = d ; year = y ; }
};
Tdate A; Tdate *B =
A.
A.num =
B->
B->num =
十、构造和析构函数
构造函数是成员函数,函数名与类名相同,函数没有返回值, 函数不需要用户调用,在创建对象的时候自动调用。
(1)如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做。
(2)只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来
(3)参数列表初始化:只有构造函数才有参数列表初始化。若要在类内声明,类外定义构造函数,且使用参数列表初始化参数时,则在类内声明的时候不允许参数列表初始化,只能类外定义的时候进行参数列表初始化
(4)函数默认参数:无论是成员函数还是构造函数,若需要类内声明和类外定义的时候,默认参数值在声明或者定义的时候都可赋值,但声明和定义的参数不能有默认值
Copy
Complex()
{ m_real =
m_imag =
} Complex(
{ m_real = a; m_imag = b; }
};
Complex A;
Complex A(
Complex *A =
Complex *A =
Copy
Student(
:name(n),number(num){
} 注意: * name、number:是本类里面的成员; * n、num:是对成员赋的值或者变量; * 不能在类里面声明的时候用参数列初始化,声明的时候可以加默认值; 对对象成员进行列表初始化:
A(
{ A a;
B(
使用原因及用处:
- 构造函数是成员函数,必须创建对象后才能调用
- 参数列表初始化是在申请空间的同时就初始化
- 如果成员是const修饰的成员、引用成员、继承时候调用父类构造函数,这几种情况就必须用参数列表初始化。
(1)如果没有自定义拷贝构造函数,系统会默认生成一个拷贝构造函数(浅拷贝构造函数,不会拷贝堆空间)
Copy
Student Jack;
Student Rose = Jack;
后面两种拷贝构造函数不会再次调用构造函数
(2)深拷贝构造函数
Copy
Student(
~Student(){
Student(Student& s) {
};
- 函数名有类一样在函数名前面添加~符号
- 析构函数没有返回值, 也没有参数
- 析构函数在对象销毁的时候自动调用(如果new一个对象,构造函数会自动执行,只有在delete的时候才调用析构函数)
例子:
Copy
Complex( )
} ~ Complex( )
} };
{ Complex a; Complex *p =
} 结果:
十一、类的内存空间
类本身是一个数据类型,在没有定义对象前是不占用内存空间的,定义对象的时候才会分配空间。
- 计算一个类的对象占用多少空间用sizeof(类名或对象)
- 类的对象大小是其数据成员(非静态数据段),和虚函数表指针(一个类里最多只能有两个指针,一个是虚函数的指针,一个是虚继承的指针)大小和。普通方法(普通函数)不占用内存,但用virtual修饰的虚函数占用一个指针大小的内存。注:一个指针的大小、内存的对齐方式和编译器有关;64位的话,大小为8;32位的话,大小为4。
- 如果一个类中没有数据成员,也没有虚表那么这个类的大小规定为 1 个字节。
十二、类继承
继承:
- 新的类从已知的类中得到已有的特征的过程
- 新类叫派生类/子类
- 已知的类叫基类/父类
- 如果直接将派生类的对象赋值给基类对象,派生类自身的成员就会被丢弃,只保留基类继承来的成员。
- 将基类指针指向派生类对象是安全的,因为派生类对象”是”它的基类的对象。但是要注意的是,这个指针只能用来调用基类的成员函数。
作用:
继承可以减少重复的代码。比如父类已经提供的方法,子类可以直接使用,不必再去实现。
类的继承格式:
Copy
{ 子类成员 };
例如:
Copy
Base() {}
};
{ };
继承方式: 公有继承, 保护继承, 私有继承
- 公有继承(public):继承时保持基类中各成员属性不变,并且基类中的private成员被隐藏。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象只能访问基类中的public成员。
- 保护继承(protected):继承时基类中各成员属性均变为protected,并且基类中的private成员被隐藏。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员。
- 私有继承(private):继承时基类中各成员属性均变为private,并且基类中private成员被隐藏。派生类的成员也只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员。
注意2: 无论那种继承子类的大小为子类+父类(所有成员都要加起来,包括私有成员)
因为有父类才有子类,所以调用顺序如下:
构造函数的调用顺序父类构造函数—对象成员构造函数—子类构造函数。
析构函数则相反。
注意:
- 当派生类的构造函数为B(){cout << “Afather\n”;}时,创建一个派生类默认会调用没有参数的父类构造函数A()。
- 如果父类构造函数带无默认值参数,派生类构造函数怎么写?
如下:
Copy
例子一: 父类构造函数
Person(
} 子类构造函数
Student( ):Person(
例子二:
Animal(
};
Cat(
};
- 父子类成员函数名相同时,不是重载,这时父类的此函数会别隐藏
- 子类调用成员函数时候会检测子类是否存在,如果存在就调用自己的, 如果不存在就调用父类的(前提是父要有这个函数)
- 如果子类和父同时存在这个函数,一定要调用父类函数,可以用(父类名::函数名( ))调用。
例如:
Copy
A(){
~A(){
} };
B(){
~B(){
} };
{ B x; x.A::fun( );
} 输出结果: Afather Bchildren father fun ~~Bchildren ~Afather
(1)语法:
Copy
{
(2)例子1:
Copy
A(){
~A(){
};
B(){
~B(){
};
C(){
~C(){
};
注意:创建子类对象构造顺序 A->B->C
如果改为:class C:public B, public A,创建子类对象构造顺序 B->A->C
(3)例子2: 如果父类构造函数带参数
Copy
继承关系
父类带参数 A(
B(
C(
(4)多个父类有同名的成员
多个父类有同名的成员, 在子类中访问会出现歧义
语法:
Copy
… };
多继承中多级继承时候多个父类同时继承同一个基类出现二义性问题–用虚拟继承解决。
例如:
Copy
};
{ D x; x.fun();
十三、虚函数、虚表
定义: 在类的成员函数声明前面添加virtual
Copy
virtual void show(){cout<endl;}
- 如果一个类中包含虚函数, 那么这个类的对象中会包含一个虚表指针vptr
- 虚表指针保存在对象空间的最前面
- 虚表中存储的是类中的虚函数地址
- 对象调用类中虚函数,会查询虚表指针再执行函数
- 一个类里最多只有两个虚表指针(一个是虚函数的指针,一个是虚继承的指针)
- 用virtual修饰的虚函数占用一个指针大小的内存。64位的话,大小为8;32位的话,大小为4。
- 同一个类的不同实例共用同一份虚函数表, 它们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.
例子1:观察输出的最后结果是什么(一定要看)
Copy
Base(){}
} };
Child(){} ~Child(){}
} };
{ Child c; Base *p = &c; p->show();
} 结果: Child::show()
注意: (
例子2:通过指针调用虚表中的虚函数(在ubuntu下运行,虚表地址通过qt调试查看)
Copy
Base(){}
} };
Child(){} ~Child(){}
} };
{ Child c;
c.show(); Fun f = (Fun)(((
f();
} 结果: Child::show() Base::show()
十四、纯虚函数(抽象函数)、抽象类
(1)纯虚函数–虚函数不需要实现直接赋值为0,纯虚函数有时称为抽象函数。
定义:
Copy
virtual void run()=0;
(2)抽象类
- 如果一个类中包含纯虚函数,那么这个就是抽象类,抽象类是不能创建对象。
- 抽象类可以派生出子类, 如果在子类中没有把父类中的纯虚函数全部实现,那么子类照样是抽象类。
例子:线程获取时间
Copy
Thread(){} ~Thread(){}
};
{ Thread th = (Thread)arg; th->run(); }
} }
Sleep(
time(&t);
} } };
{ TimeThread tth; tth.start(); TimeThread tt; tt.start();
十五、多态、虚析构
在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一消息(调用函数),不同的对象在接收时会产生不同的行为(即方法,不同的实现,即执行不同的函数)。可以说多态性是”一个接口,多种方法”。
多态性分为两类:
(1)静态多态性:在程序编译时系统就能决定调用的是哪个函数,因此又称为编译时的多态性,通过函数的重载实现(运算符重载实际上也是函数重载);
(2)动态多态性:在程序运行过程中才动态地确定操作所针对的对象,又称为运行时多态性,通过虚函数实现。
区别:函数重载是同一层次上的同名函数(首部不同,即参数个数或类型不同),虚函数是不同层次上的同名函数(首部相同)。
说明:本来,父类指针是用来指向父类对象的,如果指向子类对象,则进行类型转换,将子类对象的指针转为父类的指针,所以父类指针指向的是子类对象中的父类部分,也就无法通过父类指针去调用子类对象中的成员函数。但是,虚函数可以突破这一限制!如果不使用虚函数,企图通过父类指针调用子类的非虚函数是绝对不行的!
注意:父类中非虚函数被子类重写后,父类指针调用的是父类的成员函数,子类指针调用的是子类中的成员函数,这并不是多态!因为没有用到虚函数!
例如:
Copy
Person(){} ~Person(){}
};
ChildPerson(){} ~ChildPerson(){}
};
A(){} ~A(){}
};
{ ChildPerson cp; A a; Person p = &a; Person pson = &cp; pson->work();
p->work();
多态的时候,用父类指针指向子类对象, 在delete 父类指针的时候默认只会调用父类析构函数,子类析构函数没有执行(可能会导致子类的内存泄漏)–通过设置父类析构函数为虚函数类解决,执行子类析构函数后,自动执行父类析构函数。
例如:
Copy
Base(){
};
Der(){
~Der(){
};
{ Base *b =
十六、友元
友元:是c++里面一个特性,为了解决在函数中可以访问类的私有,或保护成员。
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类。
- 友元优点: 可以在函数中直接访问成员数据, 可以适当提高程序效率
- 友元缺点:在函数类的权限失效, 破坏了类的封装性
friend关键声明友元函数,或类。
第一种定义情况:类外定义:例如
Copy
Data() {}
};
{ Data data;
data.setA(
data.b =
} 友元声明只放类内部声明, 可以放在类内任意位置
第二种定义情况:类内定义例如:
Copy
Data() {}
{ Data data; data.a =
} };
十七、友元类
在一个类中的成员函数可以访问另外一个类中的所有成员比如在 A 类中的成员函数可以访问 B 类中的所有成员。有两种方法如下:
(1)在 B 类中设置 A 类为友元类。
(2)A::fun 要访问B类中的所有成员, 把A::fun函数声明为B类的友元函数。
(1)例如:在 B 类中设置 A 类为友元类,A类成员函数可以访问B类的protected、private成员,B类不能访问A类,如果要双向访问则要在两个类中声明对方为友元类。友元关系不能被继承。
Copy
B(){}
};
A(){}
{ b.bdata =
};
(2)A::fun 要访问B类中的所有成员, 把A::fun函数声明为B类的友元函数
Copy
};
B(){}
};
十八、运算符重载
运算符重载 关键子函数operator
1、那些运算能重载
- 双面运算符 (+,-,*,/, %)
- 关系运算符 (==, !=,
- 逻辑运算符 (||, &&, !)
- 单目运算符 (*, &, ++, –)
- 位运算符 (|, &, ~, ^, <
- 赋值运算符 (=, +=, -=, …..)
- 空间申请运算符 (new , delete)
- 其他运算符 ((), ->, [])
2、那些运算符不能重载
- .(成员访问运算符)
- .*(成员指针访问运算符)
- ::(域运算符)
- sizeof(数据类型长度运算符)
- ?:(条件运算符, 三目运算符)
注意:
3、格式:
Copy
返回类型说明符
{ 函数体 }
4、重载方式:
1.重载方式—成员函数重载
Copy
Complex C = A+B;
规定:左值是函数调用者, 右值函数的参数
2.重载方式–友元重载(普通函数重载)(可以在类里面定义,也可以在类外定义类内声明)
Copy
Complex C = A-B;
规定:左值为第一个参数, 右值为第二个参数
Copy
Complex(
Complex
C.real =
C.image =
};
Complex
C.real = A.real – B.real; C.image = A.image – B.image;
Complex C = A+B;
Complex D = A-B;
Copy
Point (
};
ostream&
{ out<<
istream&
{ in>>p.x>>p.y;
p.show();
成员函数重载
Copy
Data(
Data
Data&
}; ostream &
{ out<
{ Data A; Data d = A++;
Data &c = ++A;
友元函数重载
Copy
A(
};
A&
{ a.data +=
b.data +=
ostream&
{ out << a.data;
A b = ++a;
A d = c++;
Copy
Array(
} ~Array(){
Array(Array&
};
mArr[
转换构造函数的作用:是将一个其他类型的数据转换成一个类的对象。 当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数。 转换构造函数是对构造函数的重载。
例如:
Copy
Complex():real(
Complex(
Complex(
} Complex
};
{ Complex c; c =
c.Print();
Complex c2 = c1 +
c2.Print();
} 输出结果: test1 test3 real =
test2 test3 test2 real =
注意:
- 1、用转换构造函数可以将一个指定类型的数据转换为类的对象。但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成double类型数据)。
- 2、如果不想让转换构造函数生效,也就是拒绝其它类型通过转换构造函数转换为本类型,可以在转换构造函数前面加上explicit。
用转换构造函数可以将一个指定类型的数据转换为类的对象。但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成double类型数据)。而类型转换函数就是专门用来解决这个问题的!
类型转换函数的作用是将一个类的对象转换成另一类型的数据。
Copy
Person(
};
注意:
- 1、在函数名前面不能指定函数类型,函数没有参数。
- 2、其返回值的类型是由函数名中指定的类型名来确定的。
- 3、类型转换函数只能作为成员函数,因为转换的主体是本类的对象,不能作为友元函数或普通函数。
- 4、从函数形式可以看到,它与运算符重载函数相似,都是用关键字operator开头,只是被重载的是类型名。double类型经过重载后,除了原有的含义外,还获得新的含义(将一个Complex类对象转换为double类型数据,并指定了转换方法)。这样,编译系统不仅能识别原有的double型数据,而且还会把Complex类对象作为double型数据处理。
十九、模板函数
1、概念: 如果一个函数实现的功能类似,但是函数参数个数相同类型不同,这样就可以把实在该功能的函数设计为模板函数。
2、格式:
Copy
数据类型 函数名(参数列表){ 函数体 }
3、注意:
- (1)在编译时,根据变量生成实例。
- (2)template T只对其下面的函数模板有效。如果要定义第二个模板函数时,则要再写template 。
- (3)typename也可以用class。
- (4)T名字可以随便取。
- (5)当参数不一样时,可以这样定义参数列表template
- (6)参数列表可以带默认类型,template
- (7)模板函数只有在使用(不是运行)的时候才会检测错误。
例子1:
Copy
{ T c = a; a = b; b = c; }
mswap(a, b);
例子2:
Copy
4、模板函数与函数普通同时存在该如何调用
Copy
T c = a; a = b; b = c; }
a = b; b = c; } 调用(
mswap(a, b);
调用(
mswap(a, b);
如果模板函数和普通函数同时存在, 调用的时候会根据参数选择最优函数
二十、模板类
Copy
A() {}
T dataA; };
{ A<
注意:
- (1)如果是浮点型或者其他普通类型, 是指针或者是引用 template
- (2)参数列表可以带默认类型,template
- (3)如果使用数值为整型( char, short, int, long) 时候。template
例如: 用模板类设计一个顺序表(数组)
Copy
MVector(){
} ~MVector(){
MVector(MVector& mv){
{ out<
} out<<
T* ptr; };
ostream&
{ out<
} out<<
{ MVector<
mvs.append(
mvs<<
如果在派生子类的时候父类类没有确定class B: public A,那么子类也是模板类。
例如:
Copy
A(T a) {}
T data; };
B(T a):A
};
C():A<
};
C c;
编译时根据模板生成的不同类的静态成员是不同内存空间的;在同一个类中创建的对象的静态成员是共用一个内存空间的。
如下:
Copy
Data() {}
{ data = msg;
};
T Data
Data<
mydata.show(
Data<
Data<
二十一、强制类型转换const_cast、static_cast、reinterpert_cast、dynamic_cast
注意:以上,如果转换失败的时候会返回空
Copy
*ptr =
ra =
*x =
} 输出结果:
解释:因为a是
(1)为何要去除const限定
原因(1)是,我们可能调用了一个参数不是const的函数,而我们要传进去的实际参数确实const的,但是我们知道这个函数是不会对参数做修改的。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实际参数。
例如:
Copy
Printer(
原因(2):
还有一种我能想到的原因,是出现在const对象想调用自身的非const方法的时候,因为在类定义中,const也可以作为函数重载的一个标示符。
static_cast < type-id > ( expression )该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
- ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换,不允许不相关的类进行转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。 - ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- ③把空指针转换成目标类型的空指针。
- ④把任何类型的表达式转换成void类型。
注意: static_cast不能转换掉expression的const、volatile、或者__unaligned属性
Copy
reinterpret_cast (expression)
type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。
- reinterpret_cast可以转换任意一个32bit整数,包括所有的指针和整数。可以把任何整数转成指针,也可以把任何指针转成整数,以及把指针转化为任意类型的指针。但不能将非32bit的实例转成指针。总之,只要是32bit的东东,怎么转都行!
- 因为任何指针可以被转换到void _,而void_可以被向后转换到任何指针(对于static_cast<> 和 reinterpret_cast<>转换都可以这样做),如果没有小心处理的话错误可能发生。
例如1:
Copy
};
};
d=
C c;
结果:
例如2:
Copy
Fun fun =
fun();
dynamic_cast < type-id > ( expression )
说明: 该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void ;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
使用场景: dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
注意:
① dynamic_cast是动态转换,只有在基类指针转换为子类指针时才有意义。
② dynamic_cast<>需要类成为多态,即包括”虚”函数,并因此而不能成为void*。
③ static_cast和dynamic_cast可以执行指针到指针的转换,或实例本身到实例本身的转换,但不能在实例和指针之间转换。static_cast只能提供编译时的类型安全,而dynamic_cast可以提供运行时类型安全。
例如:
Copy
A() {}
};
B() {} ~B(){} };
{ A *p =
A *a =
B *bptr =
B *bptr1 =
二十二、异常捕捉和处理
在阅读别人开发的项目中,也许你会经常看到了多处使用异常的代码,也许你也很少遇见使用异常处理的代码。那在什么时候该使用异常,又在什么时候不该使用异常呢?在学习完异常基本概念和语法之后,后面会有讲解。
Copy
可能会发生异常的代码 }
异常处理代码 }
- throw子句:throw 子句用于抛出异常,被抛出的异常可以是C++的内置类型(例如: throw int(1);),也可以是自定义类型。
- try区段:这个区段中包含了可能发生异常的代码,在发生了异常之后,需要通过throw抛出。
-
catch子句:每个catch子句都代表着一种异常的处理。catch子句用于处理特定类型的异常。catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。
-
throw抛出的异常类型与catch抓取的异常类型要一致;
- throw抛出的异常类型可以是子类对象,catch可以是父类对象;
- catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常捕获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获;
- 如果使用catch参数中,使用基类捕获派生类对象,一定要使用传递引用的方式,例如catch (exception &e);
- 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码;
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个;
- 在try的语句块内声明的变量在外部是不可以访问的,即使是在catch子句内也不可以访问;
-
栈展开会沿着嵌套函数的调用链不断查找,直到找到了已抛出的异常匹配的catch子句。如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止。
-
实例1:抛出自定义类型异常。
Copy
Data() {} };
{ Data data;
} }
fun(
- 实例2:标准出错类抛出和捕捉异常。
Copy
输出结果:
当使用new进行开空间时,申请内存失败,系统就会抛出异常,不用用户自定义异常类型,此时捕获到异常时,就可告诉使用者是哪里的错误,便于修改。
- 实例3:继承标准出错类的派生类的异常抛出和捕捉。
Copy
FileException(
};
} }
fun(); }
当文件不存在时,输出结果:
如果在Linux上运行,上述代码需要根据环境修改:
98标准写法
Copy
~FileException()
g++ main.cpp
2011标准写法
Copy
~FileException()
g++ main.cpp –
-
使用异常处理的优点:
-
传统错误处理技术,检查到一个错误,只会返回退出码或者终止程序等等,我们只知道有错误,但不能更清楚知道是哪种错误。使用异常,把错误和处理分开来,由库函数抛出异常,由调用者捕获这个异常,调用者就可以知道程序函数库调用出现的错误是什么错误,并去处理,而是否终止程序就把握在调用者手里了。
-
使用异常的缺点:
-
如果使用异常,光凭查看代码是很难评估程序的控制流:函数返回点可能在你意料之外,这就导致了代码管理和调试的困难。启动异常使得生成的二进制文件体积变大,延长了编译时间,还可能会增加地址空间的压力。
- C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。 这个需要使用RAII来处理资源的管理问题。学习成本较高。
-
C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
-
什么时候使用异常?
-
建议:除非已有的项目或底层库中使用了异常,要不然尽量不要使用异常,虽然提供了方便,但是开销也大。
-
程序所有的异常都可以catch到吗?
-
并非如此,只有发生异常,并且又抛出异常的情况才能被catch到。例如,数组下标访问越界的情况,系统是不会自身抛出异常的,所以我们无论怎么catch都是无效的;在这种情况,我们需要自定义抛出类型,判断数组下标是否越界,然后再根据自身需要throw自定义异常对象,这样才可以catch到异常,并进行进一步处理。
二十三、STL标准模板库
容器:
- vector—顺序存储—顺序表 (访问遍历查询)
- list ——链式存储 —-链表 (适合数据长度不确定, 经常改变)
- map —-键值对存储 (key:value) (适合数据成对存储)
- set ——容器———————–(存储数据是唯一的)
Copy
names.assign(
it = names.insert(++it,
it = names.insert(++it,
it = names.insert(++it,
it = names.insert(names.end(),
it=names.erase(it); –it; } }
Original: https://www.cnblogs.com/wodehao0808/p/14654842.html
Author: 星月相随
Title: 【转】C++知识点总结
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535176/
转载文章受原作者版权保护。转载请注明原作者出处!