1.命名空间
命名空间就是为了区分不同作用域内相同名称的变量或者方法
1、命名空间用途: 解决名称冲突
2、命名空间下 可以放 变量、函数、结构体、类
3、命名空间 必须要声明在全局作用域
下
4、命名空间可以嵌套命名空间
5、命名空间是开放的,可以随时给命名空间添加新的成员
6、命名空间可以是匿名
的
7、命名空间可以起别名
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
int main ()
{
// 调用第一个命名空间中的函数
first_space::func();
// 调用第二个命名空间中的函数
second_space::func();
return 0;
}
1.使用全局变量
int atk = 1000;
void test01()
{
int atk = 2000;
std::cout << "atk = " << atk << std::endl;
// ::代表作用域 代表全局作用域
std::cout << "全局 atk = " << ::atk << std::endl;
}
2.using 声明及编译指令
int sunwukongId = 2;
//1、using声明
//using KingGlory::sunwukongId ;
//当using声明与 就近原则同时出现,出错,尽量避免
cout << sunwukongId << endl;
2.引用
引用是c++对c的重要扩充。 c++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference)。
变量名实质上是一段连续内存空间的别名,是一个标号(门牌号)
程序中通过变量来申请并命名内存空间
通过变量的名字可以使用存储空间
引用本质:
c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见
//发现是引用,转换为 int* const ref = &a;
void testFunc(int& ref){
ref = 100; // ref是引用,转换为*ref = 100
}
引用语法:
Type& ref = val;
int a =10;
int& b =a;
//b就是引用类型
特点:
&在此不是求地址运算,而是起标识作用。
类型标识符是指目标变量的类型
必须在声明引用变量时进行初始化
。
引用初始化之后不能改变。
不能有NULL引用
。必须确保引用是和一块合法的存储单元关联。
可以建立对数组的引用
。
1数组引用方式
//1.数组引用
int arr[10] ;
int (&mms)[10] = arr;
//2、先定义出数组类型,再通过类型 定义引用
typedef int(ARRAY_TYPE)[10];
//类型 &别名 = 原名
ARRAY_TYPE & pArr2 = arr;
2.函数引用
值传递
void ValueSwap(int m,int n){
int temp = m;
m = n;
n = temp;
}
引用传递
//引用传递
void ReferenceSwap(int& m,int& n){
int temp = m;
m = n;
n = temp;
}
地址传递
//地址传递
void PointerSwap(int* m,int* n){
int temp = *m;
*m = *n;
*n = temp;
}
3. 带有返回值的函数引用
1.不要返回局部变量的引用,因为会被回收
2.返回静态变量的引用
3.当函数返回为引用,函数当左值
int& fun(){
int a=10;
return a;
}
int a = fun();
cout<<a<<endl;
//多次获取结果,变量的值会被回收。
cout<<a<<endl;
//返回静态变量引用,保证变量的值正常获取
int& TestFunc02(){
static int a = 20;
cout << "static int a : " << a << endl;
return a;
}
//当函数返回为引用,函数当左值
fun() =100;
4.指针引用
Person* &pp 指针引用
// Person * &pp = pp;
void allocateSpace2(Person* &pp)
{
pp = (Person *)malloc(sizeof(Person));
pp->age = 20;
}
void test02()
{
Person *p = NULL;
allocateSpace2(p);
cout << "p.age = " << p->age << endl;
}
test02();
5.const常量引用
- 字面量不能赋给引用,但是可以赋给const引用
- const修饰的引用,不能修改
int a = 100;
const int& aRef = a; //此时aRef就是a
主要用在函数的形参,尤其是类的拷贝/复制构造函数
常量引用的好处:
引用不产生新的变量,减少形参与实参传递时的开销。
由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用。
如果希望实参随着形参的改变而改变,那么使用一般的引用,如果不希望实参随着形参改变,那么使用常引用。
3.内联函数
内联函数为了继承宏函数的效率
,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查
,又可以作为成员函数
c++从c中继承的一个重要特征就是效率。
在c中我们经常把一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。
但是在c++出现之后,使用预处理宏会出现两个问题:
- 第一个在c中也会出现,宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误。
- 第二个问题是c++特有的,
预处理器不允许访问类的成员,也就是说预处理器宏不能用作类类的成员函数
。
为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,c++引入了内联函数(inline function).
1.宏缺陷
//宏缺陷1 : 必须要加括号保证运算完整
预定义宏函数没有作用域概念,无法作为一个类的成员函数,也就是说预定义宏没有办法表示类的范围
#define MYADD(x,y) ((x) +(y))
void test01()
{
int a = 10;
int b = 20;
int ret = MYADD(a, b) * 20;
cout << ret << endl;
}
2.内联函数
在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开
,所以不需要函数调用的开销
。因此应该不使用宏,使用内联函数。
inline int func(int a){return ++;}
3.类内部的内联函数
为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但是在类内部定义内联函数时并不是必须的。任何在类内部定义的函数自动成为内联函数。
以下情况编译器可能考虑不会将函数进行内联编译
不能存在任何形式的循环语句
不能存在过多的条件判断语句
函数体不能过于庞大
不能对函数进行取址操作
内联仅仅只是
给编译器一个建议
,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数
4.函数的默认参数
函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。
如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数。
void TestFunc01(int a = 10, int b = 20){
cout << "a + b = " << a + b << endl;
}
//注意点:
//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数
void TestFunc02(int a,int b = 10,int c = 10){}
//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0,int b = 0);
void TestFunc03(int a, int b){}
5.函数的占位参数
//占位参数 只写一个类型进行占位,调用时候必须要传入占位值
//占位参数 用途
void func2(int a , int = 1)
{
}
4.类和对象
class Box{
public:
int c;
int k;
int g;
int get(){
return c*k*g;
}
void set(int cc,int kk,int gg){
c=cc;
k=kk;
g=gg;
}
};
1.构造函数
构造函数
主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 按参数类型:分为无参构造函数和有参构造函数
- 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
构造函数函数名和类名相同
没有返回值
不能有void
可以有参数。
ClassName(){}
2.析构函数
析构函数
主要用于对象销毁前系统自动调用,执行一些清理工作。
析构函数函数名是在类名前面加”~”组成,
没有返回值
不能有void
不能有参数
不能重载。
~ClassName(){}
3.拷贝构造函数的调用时机
- 对象以值传递的方式传给函数参数
- 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
- 用一个对象初始化另一个对象
4.构造函数调用规则
默认情况下,c++编译器至少为我们写的类增加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝
如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造
5.深拷贝和浅拷贝
同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝
.
当类中有指针,并且此指针有动态分配空间
,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝
。
6.初始化列表
//固定值
Person() :m_A(10), m_B(20), m_C(30)
{
}
//动态初始化
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
{
}
7 .类中类
class Person
{
public:
Person(string name, string pName, string gName) : m_Name(name), m_Phone(pName), m_Game(gName)
{
cout << "Person 的有参构造调用" << endl;
}
string m_Name; //姓名
Phone m_Phone; //手机
Game m_Game; //游戏
};
//当其他类对象 作为本类成员,先构造其他类对象,再构造自身,析构的顺序和构造相反
Person p("张三", "苹果", "王者荣耀");
8 explicit关键字
explicit关键字
c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用
9.new delete 使用
malloc 和 new 区别
malloc 和 free 属于 库函数
new 和delete属于 运算符
malloc不会调用构造函数
new会调用构造函数
malloc返回void* C++下要强转
new 返回创建的对象的指针
注意事项 :不要用void去接受new出来的对象,利用void无法调用析构函数
//堆区开辟数组,一定会调用默认构造函数
Person * pPerson = new Person[10];
//释放数组时候 需要加[]
delete [] pPerson;
//栈上开辟数组,可不可以没有默认构造,可以没有默认构造
Person pArray[10] = { Person(10), Person(20), Person(20) };
6.静态成员变量和函数
1.静态变量
是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。
- 静态成员变量必须在
类中声明,在类外定义
。 - 静态数据成员
不属于某个对象
,在为对象分配空间中不包括静态成员所占空间。
- 静态数据成员可以通过
类名或者对象
名来引用。 - 静态成员变量 所有对象都共享同一份数据
2.静态函数
静态成员函数主要为了访问静态变量,但是,不能访问普通成员变量。
静态成员函数只能访问静态变量,不能访问普通成员变量
静态成员函数的使用和静态成员变量一样
静态成员函数也有访问权限
普通成员函数可访问静态成员变量、也可以访问非静态成员变量
3.const静态成员属性
如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。
6.单例模式
保证全局程序终只有一个single对象。
#include <iostream>
using namespace std;
class Single{
public:
//1.提供对外获取对象的方法
static Single* getInstance(){
return single;
}
private:
//2.私有化函数,禁止外部创建对象
Single(){}
//3.禁止克隆对象
Single(const Single& single){}
static Single* single;
};
Single* Single::single = new Single;
int main(){
Single* s1 = Single:: getInstance();
Single* s2 = Single::getInstance();
if(s1==s2)
{ cout<<"hi"<<endl;
}
system("pause");
return EXIT_SUCCESS;
}
7.C++面向对象模型
在c语言中,“分开来声明的,也就是说,语言本身并没有支持“数据”和“函数”之间的关联性我们把这种程序方法称为“程序性的”,由一组“分布在各个以功能为导航的函数中”的算法驱动,它们处理的是共同的外部数据。
C++语言终,“数据”和“处理数据的操作(函数)”是分开存储的。
1.一个类的空间占用sizeof 是有内部变量来决定的。
2.一个null对象(内部无变量),只占一个字节。
3.类中的函数不属于类中的空间占用。
//null对象
class Person{
}
8.this 指针
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this.
9.const修饰成员函数
用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量,
当成员变量类型符前用mutable修饰时例外。
10.const 常函数
- 用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量。
- 当成员变量类型符前用mutable修饰时例外。
//常函数 修饰成员函数中的 this指针,让指针指向的值不可以修改
void showHH() const
{
m_A = 100;
//this指针的本质: const Person * const this
//this = NULL; 指针的指向不可以修改,而指针指向的值 可以改
cout << "person age = " << this->m_Age << endl;
{
11.NULL指针调用成员函数
#include <iostream>
using namespace std;
class Person{
public:
int age;
void showAge(){
//this为null对象,所以无法调用变量
if(this==NULL){//需要添加判断逻辑
return;
}
cout<<this->age<<endl;
}
void showStr(){
cout<<"this is str"<<endl;
}
};
int main(){
Person *p =NULL;
p->showAge();
//p->showStr();
system("pause");
return EXIT_SUCCESS;
}
4 继承
c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员.
子类继承父类,可以访问父类的属性和方法,除私有属性和方法外。
class Small :public Box{
public:
int getArea(){
return c*k;
}
};
父类有三种修饰:
public : 公有继承
private : 私有继承
protected : 保护继承
父类私有属性,只能自己使用
父类受保护的属性,自身和子类可以使用
父类公共的属性,都可以使用
1.继承中同名成员的处理方法
当子类成员和父类成员同名时,子类依然从父类继承同名成员
如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)
在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)
2.多继承
同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的。解决方法就是显示指定调用那个基类的版本。
3.菱形继承和虚继承
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。
这种继承所带来的问题:
- 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。
- 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
通过虚继承解决了菱形继承所带来的二义性问题
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
class Derived : public Base1, public Base2{};
4.虚继承实现原理??
5 重载函数和重载运算符
5.1函数重载
方法名相同,方法参数或者个数、顺序不一样
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
5.2 运算符重载
可重载的运算符
5.2.0 加号运算符重载
//利用全局函数实现加号运算符重载
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
5.2.1 自增自减(++/–)
注意前置和后置的返回值类型不一致。
//前置++ 重载
MyInter& operator++()
{
this->m_Num++;
return *this;
}
//后置++ 重载
MyInter operator++(int)
{
//先记录初始状态
MyInter temp = *this;
this->m_Num++;
return temp;
}
5.2.2 指针运算符(*、->)重载
class SmartPoint
{
public:
SmartPoint(Person * person)
{
this->m_person = person;
}
//重载->运算符
Person * operator->()
{
return this->m_person;
}
//重载 * 运算符
Person& operator*()
{
return *m_person;
}
~SmartPoint()
{
if (this->m_person)
{
delete this->m_person;
this->m_person = NULL;
}
}
private:
Person * m_person;
};
//利用智能指针 管理 new出来的person的释放操作
SmartPoint sp(new Person(18));
sp->showAge(); // 本质sp->->showAge(); 编译器简化为 sp->showAge();
(*sp).showAge();
5.2.3 赋值(=)运算符重载
//重载 =
Person& operator=( const Person &p)
{
//先判断原来堆区释放有内容,如果有先释放
if (this->m_Name != NULL)
{
delete [] this->m_Name;
this->m_Name = NULL;
}
this->m_Name = new char[strlen(p.m_Name) + 1];
strcpy(this->m_Name, p.m_Name);
this->m_Age = p.m_Age;
return *this;
}
5.2.4 等于和不等于(==、!=)运算符重载
bool operator==( Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
bool operator!=(Person & p)
{
return !(this->m_Name == p.m_Name && this->m_Age == p.m_Age);
}
5.2.5 函数调用符号()重载
class MyAdd
{
public:
int operator()(int a,int b)
{
return a + b;
}
};
5.2.6 符号重载总结
=, [], () 和 -> 操作符只能通过成员函数进行重载
<< 和 >>只能通过全局函数配合友元函数进行重载
不要重载 && 和 || 操作符,因为无法实现短路规则
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
6 多态
c++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。
1.父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象
动态多态产生条件:
//先有继承关系
//父类中有虚函数,子类重写父类中的虚函数
//父类的指针或引用 指向子类的对象
1.虚函数
在基类中使用关键字 virtual 声明的函数。在子类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
class Animal
{
public:
//虚函数
virtual void speak()
{
cout << "动物在说话" << endl;
}
virtual void eat(int a )
{
cout << "动物在吃饭" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
void eat(int a)
{
cout << "小猫在吃饭" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
2.纯虚函数
在基类中加入至少一个纯虚函数(pure virtual function),使得基类称为抽象类(abstract class).
纯虚函数使用关键字virtual,并在其后面加上=0。如果试图去实例化一个抽象类,编译器则会阻止这种操作。
当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类。
Virtual void fun() = 0;告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址。
3.虚析构函数/纯虚析构函数
虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象.
纯虚析构函数和非纯析构函数之间唯一的不同之处在于纯虚析构函数使得基类是抽象类,不能创建基类的对象。
virtual ~People(){
cout << "析构函数 People!" << endl;
}
7.模板
模板是泛型编程的基础。实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。
c++提供两种模板机制:函数模板和类模板。
- 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
- 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为
7.1.函数模板
//交换int数据
void SwapInt(int& a,int& b){
int temp = a;
a = b;
b = temp;
}
1.函数模板和普通函数在一起调用规则
c++编译器优先考虑普通函数
可以通过空模板实参列表的语法限定编译器只能通过模板匹配
函数模板可以像普通函数那样可以被重载
如果函数模板可以产生一个更好的匹配,那么选择模板
//函数模板
template<class T>
T MyPlus(T a, T b){
T ret = a + b;
return ret;
}
//普通函数
int MyPlus(int a, int b){
int ret = a + b;
return ret;
}
2.函数模板机制结论
编译器并不是把函数模板处理成能够处理任何类型的函数
函数模板通过具体类型产生不同的函数
编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
7.2.类模板
类模板用于实现类所需数据的类型参数化
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
public:
NameType mName;
AgeType mAge;
};
Person<string, int>P1("德玛西亚", 18);
P1.showPerson();
1.类模板做函数模板
#include <iostream>
#include <string>
using namespace std;
template<class Name,class Type>
class Person{
public:
Person(Name name,Type type){
this->name = name;
this->type = type;
}
void print(){
cout<<"name="<<this->name<<" type="<<this->type<<endl;
}
public:
Name name;
Type type;
};
void doSome(Person<string,int> &pp)
{
pp.print();
}
int main()
{
Person<string,int> pp("hi",10);
doSome(pp);
system("pause");
return 0;
}
2.类模板–派生类模板
在这里插入代码片
3.类模板–类内-类外实现
4.类模板–头文件和源文件分离
5.模板类和友元函数
8. 类型转换
类型转换(cast)是将一种数据类型转换成另一种数据类型
静态转换(static_cast)
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证
//基础数据类型转换
void test01(){
char a = 'a';
double b = static_cast<double>(a);
}
class Animal{};
class Dog : public Animal{};
class Other{};
//继承关系指针转换
Animal* animal01 = NULL;
Dog* dog01 = NULL;
//子类指针转成父类指针,安全
Animal* animal02 = static_cast<Animal*>(dog01);
//父类指针转成子类指针,不安全
Dog* dog02 = static_cast<Dog*>(animal01);
//继承关系引用相互转换
Animal ani_ref;
Dog dog_ref;
//继承关系指针转换
Animal& animal01 = ani_ref;
Dog& dog01 = dog_ref;
//子类指针转成父类指针,安全
Animal& animal02 = static_cast<Animal&>(dog01);
//父类指针转成子类指针,不安全
Dog& dog02 = static_cast<Dog&>(animal01);
动态转换(dynamic_cast)
dynamic_cast主要用于类层次间的上行转换和下行转换;
- 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
- 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全;
- 不支持基本数据类型转换
- 父类不能转成子类,因为不安全
//继承关系指针
Animal* animal01 = NULL;
Dog* dog01 = new Dog;
//子类指针转换成父类指针 可以
Animal* animal02 = dynamic_cast<Animal*>(dog01);
animal02->ShowName();
//父类指针转换成子类指针 不可以
//Dog* dog02 = dynamic_cast<Dog*>(animal01);
//继承关系引用
Dog dog_ref;
Dog& dog01 = dog_ref;
//子类引用转换成父类引用 可以
Animal& animal02 = dynamic_cast<Animal&>(dog01);
animal02.ShowName();
常量转换(const_cast)
该运算符用来修改类型的const属性
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象
注意:
不能直接对非指针和非引用的变量使用const_cast操作符去直接移除它的const.
//不可以将非指针或非引用做const_cast转换
const int * p = NULL;
int * pp = const_cast<int*>(p);
const int * ppp = const_cast<const int *>(pp);
//const int a = 10;
//int b = const_cast<int>(a);
int num = 10;
int &numRef = num;
const int& num2 = const_cast<const int&>(numRef);
重新解释转换
这是最不安全的一种转换机制,最有可能出问题。
主要用于将一种数据类型从一种类型转换为另一种类型。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针.
9.异常处理
异常处理就是处理程序中的错误。所谓错误是指在程序运行的过程中发生的一些异常事件
1.捕获异常
int main(){
try {
int x=5;
int y=0;
int z = x/y;
cout<<z<<endl;
} catch (const char* e) {
cout<<e<<endl;
}
return 0;
}
抛出异常
int divsion(int a,int b){
if (b==0) {
throw "string is null";
}
return a/b;
}
栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
异常变量生命周期
throw的异常是有类型的,可以是数字、字符串、类对象。
throw的异常是有类型的,catch需严格匹配异常类型。
接口抽象类
接口描述了类的行为和功能,而不需要完成类的特定实现。
//如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类
class Shape{
public:
int aa;
int bb;
void a();
//纯虚函数
virtual int b() = 0;
private:
void c();
};
class Circle :public Shape{
public:
int b(){
return 9;
}
}
文件读写
#include <fstream>
#include <istream>
#include <iostream>
using namespace std;
int main(){
char ret[1000];
// 文件写入操作
ofstream out;
// 文件打开模式
out.open("../hell.txt");
cout<<"输入内容:"<<endl;
// 写入固定内容
out<< "this is file"<< endl;
out<< "this is line\n"<< endl;
//写入输入的内容,ret作为缓存区
cin.getline(ret,100);
out<<ret<<endl;
// 关闭文件
out.close();
// 文件读取操作
ifstream ifile;
ifile.open("../hell.txt");
cout<<"文件内容:"<<endl;
while(!ifile.eof()){
ifile.getline(ret,1000);
cout<<ret<<endl;
}
ifile.close();
return 0;
}