#C++笔记
c++:支持面向对象编程,泛型编程,过程化编程
常用于系统开发,引擎
c++简介:
共有和私有成员:
类的保护成员:
内联函数:inline,相当于调用的函数在主函数中展开,
函数代码太长不要用内联,也尽量不要用循环
友元:帮助你访问对象的私有成员
赋值运算符重载:两个结构体类型相同时,可以用结构体名直接赋值
(相当于在赋值函数符里封装了strcpy之类的机制
所以当结构体成员有数组元素时,也能赋值过去)
虚函数:
多重继承:继承父亲和母亲的特征
抽象类;
静态成员函数:
const成员函数:const 写在函数后面
模板(template):应用于泛型编程
标准模板库(STL)
名字空间
c++新增特性:
更为严格的类型检查,
新增变量引用
支持面向对象
*类和对象,继承,多态,虚函数及RTTI(运行时类型识别)
异常处理:
支持模板,标准模板库
OOP(面向对象编程)
对象:*可以对其做一些事情的一些东西,一个对象有状态,行为和标识三种属性
类:共享相同属性和方法的对象集合,类的方法和属性被称为成员
封装:
继承:
组合:
泛型编程:需要模板
编译c++程序的步骤和c程序是相同的
但是编译命令不同:c++程序用g++编译
c++文件:以 . cpp 结尾
固定写法:
#include <iostream>
using namespace std;
使用名字空间</iostream>
头文件不用写扩展名.h
强制类型转换
类型名(表达式)
int()
变量引用reference
取别名
语法:数据类型 & 别名=变量名
int a=10;
int & b=a;//不能只声明引用而不初始化
c++引用了输入输出对象,包含在头文件
cout: 作用与输出流
cout是一个对象
std ::cout <<"hello taingou"<<"en"
<<:将后面的东西打印到屏幕上,
endl:换行
:: 作用域运算符
输出:hello tiangou en
// 按值传递
//按地址传递
//按引用传递
void swap3(int &a ,int &b)
{
int temp=a;
a=b;
b=temp;
}
int main()
{
int a=10,b=15;
swap3(a,b);
}
交换a,b的值
如何让引用指向一个数据(常量):
int &ref =10;//引用非法,虽然10有空间,但是没名字
const int&ref=10;//正确用法
1)const 的引用可以指向常量 ,编译器创建一个临时变量来存放这个常量
然后引用去指向这个临时变量,然后临时变量名无效
内联函数:节省运行时间
使用语法:inline 存储类型 数据类型 函数名(参数列表)
函数重载:C++中函数名可以相同,
重载条件:但是参数列表一定不能相同(可以是个数不同,也可以是顺序不同)
默认参数:
在函数声明或定义中的时候给定一个参数或多个参数默认值
默认参数是在函数调用时没有给对应位置传递参数时才起作用
注意1):如果一个参数带了默认值,那么这个参数后面的参数都要带默认值
注意2):一个函数不能既能重载又有默认参数(二义性)
int func(int a,int b=90)
{
return a+b;
}
int main()
{
int x=5,y=10;
cout<<fun(x)<<endl;
}
普通引用不能引用常量引用
引用与指针的差别
指针是一个实体,而引用仅是个别名;
“sizeof 引用”得到的是所指向的变量(对象)的大小
,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
void func(int &ref)
int *const ref=&a;
int &ref 和 const int &ref 不一样
一个指向变量空间,一个指向常量
函数返回引用
int &func()
函数返回引用不应该返回局部变量,因为空间会释放
返回正确引用的函数可以做为左值:func()=xxx;
作用域:
类域:
名字空间域:namespace 域名//一般写在函数体外
{
成员1;
}
::如果没有左值,代表访问全局的对象
用(域名+::)访问
定义名称空间:
namespace 名称
{
声明空间下的变量和函数;
}
使用声明:
using 名称::变量
using 名称::函数名
五种存储类
自动,寄存器,具有外部链接的静态,具有内部链接的静态,空链接的静态
动态内存:
new/delete来分配内存
初始化:int p=new int(50);//开辟了一个空间,并且初始化(p=50)
int main()
{
int *p=new int ;//开辟一个int型的空间
int *q=new int[100];//开辟了一段连续的存储空间 delect p; delect [] q;//释放连续地址
}
定位运算符
new跟圆括号表示定位
char buf[512];//有一片连续的空间
new(buf +n) int//new返回一个指向任意类型(void *)的指针
int 相当于强制类型转换
传递一个地址给它,它会返回一个void*的地址给你
char buffer[500]; int *ptr=new(buffer+sizeof(int) * 10) int;
=================================================================
c++中的输入输出:会自动 做类型转换
C:printf("xxxxxxx");
C++:
输出:cout<<"xxxxxxx";
cout:输出对象,流对象,自动类型识别
<<:插入运算符
换行:
控制符:endl
c++的输出和C语言的printf不同
cout输出时,自动规划类型输出
输入:cin>>
int val;
char c;
cin>>val>>c;
会以空格或回车作为一个变量输入结束
格式控制符:
设置状态标志流成员setf
一般格式:long ios::setf(long flags)
调用格式:流对象.setf(ios::状态标志)
清除状态标志流成员函数unsetf
一般格式:long ios::unsetf(long flags)
调用格式:流对象.unsetf(ios::状态标志);
设置域宽流成员函数width
一般格式:int ios::width(int n)
调用格式:流对象.width(n);
注意:它只对下一个输出流有效,输出完成后,恢复默认值0
设置实数的精度流成员函数precision
一般格式:int ios::precision(int n)
调用格式:流对象.precision(n);
注意:参数n在十进制小数输出时表示有效数字
标志fixed和scientific表示输出小数位数
填充字符流成员函数fill
一般格式:char ios::fill(char ch)
调用格式:流对象.fill(ch);
注:当输出值达不到域宽时用填充符来填充,
默认填充符为空格,与width函数搭配使用。
类(抽象的概念)和对象(类的实体化):
类的语法:
class 类名{
权限:属性/行为
};
类名定义的实体叫对象
三大特性:
封装 多态 继承
封装:把属性和行为糅合在一起,加以控制
类是抽象的,类定义后的对象是实际的例子,这一过程称为实例化
class demo{//class:关键字,demo类名
int a;
int b;
};
demo obj;//声明类对象与C语言中struct的区别;
1)struct demo obj;表示声明一个结构体,但是demo不能单独存在
如果通过类来实现类对象,类名可以单独存在的
2)C语言结构体默认为共有的 类:私有的成员,不给权限无法访问 3)C语言结构体里不能出现函数的声明,只能通过函数指针间接去指向它,而C++类里面可以包含函数声明
public:共有权限(类内和类外都能访问,主要形容一些方法)
private:私有权限(类内可以访问,类外不能访问---除了友元)
protected:保护权限(类内可以访问,类外不能访问,!!可以让子类访问)
OOP的三个基本特征:封装,继承,多态
封装:使得代码模块化
继承:扩展已存在的代码模块,代码重用
多态:接口重用
构造和析构:!!!创建对象时一定会调用构造函数,释放对象时一定会调用析构函数
构造函数:{
默认构造:当我们没有手动声明定义函数时,编译器自动添加一个构造函数
构造函数和类名一摸一样,没有返回类型(根本不用返回类型)
构造函数可以重载,以不同的方式给对象初始化 !!如果我们手动写了有参构造,那么编译器不再提供无参构造,但会提供拷贝构造 }
拷贝构造:
传递相同类型的对象对另一个创建时的对象初始化
Person p1=p2; //等于 Person p1(p2);
Person p3=Person("xxx",xxx);//编译器可能会创建一个临时对象,通过有参构造初始化(有参函数参数:const Person &p)临时对象,然后把p3指向临时对象的空间
(好处:在于不用过多的创建和释放)
析构函数:释放时调用(一定不能有参数,而且析构函数不能重载)
{
格式:和类名一样,只是前面会加一个~,同样没有任何返回类型
}
意义:构造函数的意义就是为了初始化对象,
析构函数就是为了清理对象后的垃圾通常在构造函数中遇见堆区开辟空间的问题时,会涉及到深浅拷贝
浅拷贝和深拷贝:
浅拷贝:单纯的赋值操作,直接把堆区地址拷贝给另一个对象
(在堆区中两个对象指向同一片空间,当析构函数调用时,很有可能出现重复释放)
深拷贝:让两个对象各自指向独立的空间
string name;
int *age;
//这是浅拷贝,直接把堆区地址拷贝给另一个对象
Person(const Person &p){
name = p.name;
age = p.age;
cout << "copy constructor call" << endl;
}
//这是深拷贝,让两个对象各自指向一个独立的空间
Person(const Person &p){
name = p.name;
age = new int(*p.age);
}
void Show(){
cout << "name : " << name << ", age = " << *age << endl;}
~Person(){
if(age != NULL){
delete age;
age = NULL;
cout << "destructor call" << endl;
}
}
this指针:(仅在类内中使用)
指向类对象自身(当前对象)的首地址
每个类对象成员函数都有一个this指针,指向调用对象,
如果要引用整个对象,则用*this
person p1=person(xxx);可能会创建临时对象p2=person(xxx);一定会创建临时对象
!!!struct和class的区别
struct:默认权限为共有的,不能在结构体里声明函数,
只能声明函数指针来间接访问函数
class:默认权限是私有的,可以在类内声明函数
函数不属于类,所以在算类的字节长度时,不用管函数的大小
=================================================================
c++day3:
this指针:(仅在类内中使用)
指向类对象自身的首地址
每个类对象成员函数都有一个this指针,指向调用对象,
如果要引用整个对象,则*this
person p1=person(xxx);可能会创建临时对象p2=person(xxx);一定会创建临时对象
!!!返回引用,则相当于返回同一片空间。
其他返回临时对象
static:可以将类的成员属性和成员函数声明成静态的
使用:可以通过类对象来调用静态成员变量
也可以通过作用域来调用静态成员变量,最好用作用域
例子:
#include <iostream>
using namespace std;</iostream>
class demo{
public:
static int a;
int b;
static int getvalue();
};
int demo::a=10;
int demo::getvalue()
{
return a;
}
int main()
{
demo d1;
d1.b=200;
// cout<<d1.a<<endl;
cout<<demo::a<<endl;
// cout<<d1.getvalue()<<endl;
cout<<demo::getvalue()<<endl;
}
1)(静态成员)static修饰的成员变量,存放在静态区,是独立的存在
没有包含在类里面 ,不属于任何类对象,所有人都可以访问它
2)!!静态成员变量必须在类外初始化
数据类型 类名::变量名=数值;
例子:
假如demo里面有一个static int a;
想要初始化a的话,必须在类外,int demo::a=10;
3)静态变量的释放问题
问题:静态变量不属于类
4)静态成员没有对象的this指针,因为它不属于任何对象
5)静态成员不和具体的对象关联,也不能直接访问类的其他成员
(静态成员不能区访问非静态的成员)
const 成员函数:
数据类型 函数名(参数列表)const;
可以保证成员函数不会修改对象
使用语法:函数()const;
表示如果不需要在函数体内实现成员变量的修改,
可以将函数设为const
const对象:
类名 const 对象名
表示对象为只读状态,常对象中的所有成员的值都不能够修改
const成员变量:
在构造函数初始化的时候要用初始化列表,不能显示的进行赋值
const int i ;
Demo(int val):i(val){}//构造函数
关键字:mutable:声明的变量在常函数中可以被更改,普通的变量就不行
mutable int i;
友元:
用途:可以访问私有成员
friend引导的友元函数或类,没有权限限制
(写在public或者private都可以)
关键字:friend
三种方式:
1)全局函数作为友元:
在类里面声明 friend 数据类型 函数名(参数列表)
,在类外进行函数的定义具体实现
2)类作为友元(比较常用)
友元不能被继承
友元关系是单向的,类A是类B的友元,但类B不一定是类A的友元
在类里面声明 friend class 类名;
3)类成员函数作为友元
在类里面声明 friend 数据类型 类作用域::函数名(参数列表)
运算符重载:
string a="hello";
string b="world";
cout<<a+b<<endl;//hello world
cout<<2+5<<endl;//7
运算符重载:
只能重载已经预定义好的系统运算符,不能用户自定义新的运算
语法:
类里面的声明:<返回类型说明符>operator<运算符>(<参数列表>)
i类外进行运算符重载函数的定义说明(一定记得加上作用域)
只要在类内声明的重载函数,在类外定义说明时一定的加上作用域
也可以直接在类外声明定义,注意权限问题(友元):
-fno-elide-construtors(在编译时加上这个语句,可以使编译器不优化,按部就班进行)
temp为函数内的临时局部类
Person p=p1+p2;//p1+p2返回temp
不会调用拷贝构造的原因是编译器做了优化,
p直接去占了临时对象temp的空间(地址),这样节省了空间
一般类中,
系统默认情况下会提供默认构造,默认析构 ,,默认拷贝构造,(浅拷贝)运算符重载=
弊端:深拷贝和浅拷贝的区别
浅拷贝相当于两个指针变量指向同一片空间 (比如=)
深拷贝:新开辟一个空间来存放拷贝的内容
必须在堆区进行,所以必须进行操作地址的初始化
(可以在构造函数里将指针指向new int(一个int型的堆区空间)),
使其在堆区,不然进行释放地址时会出错。
实现<<(插入运算符)
将插入运算符右边的东西插入到cout中打印在屏幕上//p为定义的一个类
ostream &operator<<(ostream &out,const person &p);//简化后为cout <<p;
(必须在类外声明定义):因为不是我自己写的类对象来调用,而是cout进行调用
如果在类内声明(则还得在类外进行定义):
//operator<<(const person &p);
对象p1去调用类里面的operator函数,简化后表达式为 p1<<p;无法实现
//operator<<(ostream &cout);对象p1去调用类里面的operator简化后为 p1<<cout;无法实现
++重载:
//声明在类内,定义实现在类外
operator++(); //没有参数列表表示前++
operator++(int); //int表示占位符,表示后++
前++:
定义实现例子:person & person::operator++()
{
this->m+=1;
return *this;
}
后++:返回临时变量引用的话在后续过程中会出现问题,(不能返回局部变量的引用)
所以直接返回类对象的内容
定义实现例子:person person::operator++(int)
{
person temp=*this;
this->m+=1;
return temp;
}
占位符:能帮助你重载函数,调用时需要传递参数
=================================================================
继承:在一个已存在的类的基础上建立一个新的类(子类),以存在的类称为基类或父类,新建立的类称为派生类或子类。
一个新类从已有的类那里获得其已有的属性特征,这种现象叫继承
子类会继承父类所有的数据成员和成员函数
继承语法:
class son:继承方式 父类名//son:子类名
继承方式(三种):
公有继承(public):
基类中的public成员在子类中也是public,protected成员在子类中也是保护成员,父类的私有成员,在子类中也是私有成员,且子类以公有方式继承也不能访问。
私有继承(private):
基类中的public成员和protected成员都变成了子类的私有成员,子类还是无法访问父类私有成员
保护继承(protected):
基类中的public成员和protected成员在子类中会变成保护成员,子类保护继承也不能访问父类的私有成员。
!!基类的构造函数,析构函数,运算符重载,友元是不会被子类继承的
如果要通过子类访问父类的同名成员,需要加上作用域
!!如果父类只有有参构造,在写子类的构造函数时要注意调用父类的有参构造
父类:animal 子类:fish
class fish:public aniaml{
public:
class fish(string str,int a):animal(str,a){}
};
string类:
使用memset和strlen这些加头文件#include
getline函数:按行读取
is-a:继承关系
has-a;类b包含类a的关系
多重继承:多个基类
class son:public father,public mother{};//以,分隔
类对象从左边依次调用构造函数构造,析构从右到左
菱形继承:
一个基类派生出两个派生类,又有一个派生类继承于两个子类
孙子类会继承两个子类的同名成员(来自于父类),再使用这个成员就会产生二义性。
virtual:虚继承,大家共享父类的数据
语法:class son:virtual 继承方式 父类
作用:为了解决菱形继承带来的二义性
使用方法:在继承方式前面加virtual
!!!面试重点
封装(wrap):
实现细节隐藏,使得代码模块化。把成员数据和成员函数封装起来,通过公共的成员接口进行成员数据的操作。
继承(inheritance):
扩展已存在的代码,目的是为了代码重用
多态(polymorphism):
目的是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
=================================================================
¥多态:就是接口重用。意思就是,无论传递过来的究竟是哪个类的对象,函数都能通过同一个接口调用到适合各自对象的实现方法
向上转换:父类的引用或者指针可以指向子类(子类会向上转换成父类可以匹配的类型),子类的引用或者指针无法指向父类(因为子类会在父类的基础上添加了很多东西,父类无法识别)
A)静态多态:地址早绑定(函数地址在编译阶段就实现了绑定)
B)动态多态:在程序运行阶段的时候才确定函数地址,编译时给的地址不会被绑定(使用虚函数(在函数声明前加上virtual)实现,类对象会创建一个虚函数表存放虚函数地址,编译器在编译阶段就不会绑定虚函数的地址,而是在使用阶段,看对象是指向什么类型下的虚拟函数表(vftable),指向谁就用谁)
!!地址早绑定会根据指针类型来执行,有虚函数了就不会管指针类型,而是看虚函数指针指向的地方。
1)将父类的同名函数前加上virtual,在编译时不绑定函数的地址,并且类里面会生成一个虚函数指针指向自己生成的虚拟函数表,后面虚函数指针指向哪个虚函数就执行哪一个函数
2)定义一个函数用父类的引用去指向子类的对象
test(animal& ani)
{
ani.speak();
}
//!!先有对象,才有虚函数表,虚拟指针,不能调用其他人的虚函数指针和虚函数表
3)使用时传递子类给(test函数),让父类的引用去指向相应的类进行调用,子类也会继承父类的虚函数指针和虚函数表,但是子类里重写了同名函数,子类的虚函数表就会变成自己的同名函数,最后就会执行相应子类的同名函数
vfptr:虚拟函数指针(指向虚拟函数表)
虚函数:
用法:在函数的声明前加上virtual,定义函数时不需要使用关键字
1)非类的成员函数和类的静态成员函数不能定义为虚函数
2)将父类的函数声明为虚函数时,子类的同名函数会自动成为虚函数,可以加virtual也可以不加
3)构造函数不能定义为虚函数,析构函数可以
纯虚函数:
(无法调用,无法创建类) virtual void speak()=0;
要实现多态,父类的函数肯定会声明为virtual。但一般不在意父类虚函数了什么,所以可以定义为纯虚函数
语法:virtual 类型 函数名(参数列表)=0;
成员函数覆盖(重写)
指派生类重新定义基类的虚函数,不同作用域中,参数相同的函数,基类函数必须有virtual关键字,不能有staic
成员函数重载:
在同一个作用域(类)中,函数名相同,参数不同
成员函数隐藏(重定义)
不同作用域同名函数:
1)参数不同:基类的函数将隐藏
2)参数相同:但是基类函数没有virtual关键字,基类的函数被隐藏
抽象类:
作用:提供给系统派生出派生类
含有纯虚函数的类就是抽象类。抽象类无法创建对象。子类继承后重写了该函数,子类可以创建对象
注:子类在创建的时候编译器可以调用父类的构造函数(所以通过这个特性可以直接构造子类)
抽象类的特征:
1)含有纯虚函数的类
2)没有完整的信息,只能是派生类的基类
3)抽象类不能实例化创建对象,不能有静态成员
4)派生类应该实现抽象类的所有方法
限制构造函数:
类的构造函数访问权限不是public
虚析构:(在堆区涉及的比较多)
基类在指向派生类对象的时候,基类对象结束并不会引起派生类的析构函数的调用
所以把父类的析构函数声明为虚函数类型,避免内存泄漏,如果父类的析构声明为纯虚函数,但是父类里需要释放空间(构造函数可能会开辟空间),那么也应该在类外进行定义。
子类会继承虚析构的相应特征,释放内存时,普通函数根据指针的类型来调用相应类的析构函数,如果父类是虚函数,就不考虑指针的类型了,而是考虑虚函数指针的指向(指向自己相应的虚函数表)
泛型编程:(重用代码)
从定义一个通用类型的函数或类,使之可以接收任意类型,在具体使用时传递具体的类型
模板:支持参数化多态的工具,就是让类或函数声明成为一种通用类型
!!模板只能在全局定义
A)函数模板:
语法:
template<typename/class T>
函数的定义
T fun(T a);
template:表示后面的为模板类型typename/class:表示任意类型的声明T:通用的类型,可以是其他字符或字符串,一般大写
编写模板函数时,通用类型可以替代返回类型,参数的数据类型,以及局部变量的数据类型
模板分为:
(模板不能实现隐式转换(不知道T是转换的,还是被转换的),普通函数可以)
1)自动类型推导:根据传递的实参,编译器来确定模板应该使用什么类型的实例
2)显示指定类型:在使用函数模板时,在函数名后写<具体类型>
模板和普通函数可以重名,甚至参数个数也一样(相当于重载),普通函数优先级高于模板函数(当实际调用时,传递的实参和普通函数模板函数都匹配时,优先调用普通函数)
a)隐式实例化:(相当于在后台进行的实例化)
当程序中有函数模板,在具体调用之前,编译器并不会编译这个模板,调用时,如Add(a,b),模板就接受了int型的数据,这个时候才确定模板的类型,才会编译这个模板
b)显示实例化:(相当于把类型写出来,能看到)
程序中存在一个模板,但也可以在调用之前就显示的指定模板类型,之后调用是显示实例化的方式调用
template<class t="">
T func(T a, T b)
{
return a+b;
}
显示实例化:template int func<int>(int a,int b);
显示实例化会指定具体的类型,但还是依赖于模板(需要模板来进行初始化)</int></class>
=================================================================
c)显示具体化:当模板不能处理传入类型时,可以定义显示具体化(具体传入的类型,他们相匹配),具体化不依赖模板,所以需要定义
显示具体化:(用于模板识别不了的类型)会指定类型,但是不依赖模板(优先级比模板高一点,但是比普通函数低)
显示具体化:template <> boll mycompare(person &a,person &b)
{
}
bool类型:返回true或false
模板例子简单实现:
#include<iostream>
using namespace std;</iostream>
template<typename t="">
T func(T &a,Y &b)
{
T c=a+b;
return c;
}
int main()
{
int a=3,b=6;
cout<<func(a,b)<<endl;
return 0;
}</typename>
B)类模板:
通用的类的模板-类不指定类型
语法: temlate <typename/class T> calss 类名 调用时必须加上<类型>,显示的指定它是什么类型,因为类模板不会自动类型推导。
类模板可以有默认参数,但函数模板没有
template<class T1,class T2>//按照模板类的定义灵活指定参数类型
void test2(person <T1,T2> &per)
{
cout<<per.name<<endl<<per.age<<endl;
}
template<class t="">//将整个类灵活定义成T
void test3(T &per){
cout<<per.name<<per.age<<endl;
}</class>
int main()
{
person<char,int> p1(65,5); test2(p1); return 0;
}
在类外定义成员函数,作用域需要加上模板类型
template <class T1 , T2>
person<T1,T2>::
在子类中调用父类的构造函数:加上父类。。。
!!类模板下的成员函数的定义和声明不应该放在不同的文件下,这样在编译的时候找不到,所以c++允许一个文件的后缀叫.hpp(可以把声明和定义都放在一个文件内)
模板类的继承:
#include <iostream>
#include <string>
using namespace std;</string></iostream>
template <class t="">
class Base{
public:
T m_obj;</class>
Base(){}Base(T m){ m_obj = m;}void show(){ cout << m_obj << endl;}};
//继承模板类需要指定类型
class Son:public Base<int>{
public:
Son(int m):Base(m){}
}; </int>
//灵活继承,需要子类也是一个模板
template <class T1,class T2 =int>
class Son2:public Base<t1>{
public:
T2 obj;</t1>
Son2(T1 m,T2 n):Base<T1>(m){}};
int main()
{
Son s1(100);
cout << s1.m_obj << endl;
Son2<string, int> s2("longgiegie", 48);cout << s2.m_obj << endl;}
===================================================================
day8:
转换函数:(无返回值,无参数)
!!但是在函数里必须return。
转换函数必须是成员是成员函数
转换函数不需要参数(在名字上就告诉了系统你需要转换的类型)
{
调用类的构造函数,这个构造函数只能有一个参数,而且参数类型要和 数值的类型一样。
创建一个临时对象,然后将临时对象赋值给左值对象。
explicit(显示的):修饰构造函数,防止构造函数自动转换,但可以手动强制转换
强制类型转换:
类对象=(类名)数值 或 类对象=类名(数值)
1)系统借助一个参数而且类型匹配的构造函数,将简单的数据类型(编译器内置类型)转换成类类型(隐式转换)
2)从类类型到编译器内置类型:比如class to int
语法:
operator type()const{}//type:转换的类型
//将类对象转成type型
!!注意没有返回类型,但是函数体内要写返回值,返回type类型
系统内置给了你一个相应的返回类型,但是你不能写在函数前面
在main函数里可以用;
type 变量=类对象;
}
例1:将类转换成数据
1#include <iostream>
2 using namespace std;
3 class person{
4 public:
5 person()
6 {
7 cout<<"none arguments constructor"<<endl;
8 }
9
10 person(double d,int flag)
11 {
12 this->d=d;
13 this->flag=flag;
14 cout<<"two arguuments constructor"<<endl;
15 }
16
17 operator double()const
18 {
19 cout<<"zhuan huan hanshu"<<endl;
20 return this->d*2;
21 }
22
23 void show()
24 {
25 cout<<"d="<< this->d <<endl;
26 cout<<"flag="<< this->flag <<endl;
27 }
28 public:
29 double d;
30 int flag;
31 };
32
33 int main()
34 {
35 person p1(19.7,50);
36 double data=p1;
37 cout<<"data="<<data<<endl;
38 return 0;
}</iostream>
例2:将数据转换成类
#include
using namespace std;
class Demo{
public:
double dle;
int flag;
Demo();
Demo(double dle);
Demo(double dle, int flag);
// Demo operator+(const Demo &d);
friend Demo operator+(const Demo &d1, const Demo &d2);
//convert functions
//operator int() const;
//operator double() const;
};
Demo::Demo()
{
cout << "constructor" << endl;
}
Demo::Demo(double dle)
{
this->dle = dle;
cout << "one argument constructor" << endl;
}
Demo::Demo(double dle, int flag)
{
this->dle = dle;
this->flag = flag;
cout << "two argument constructor" << endl;
}
if 0
Demo Demo::operator+(const Demo &d)
{
Demo temp;
temp.dle = this->dle + d.dle;
temp.flag = this->flag + d.flag;
return temp;
}
#endif
Demo operator+(const Demo &d1, const Demo &d2)//不写返回值的原因是因为 friend引导的不在类里面,只是说明一下
{
Demo temp;
temp.dle = d1.dle + d2.dle;
temp.flag = d1.flag + d2.flag;
return temp;
}
if 0
Demo::operator double() const
{
return dle*9;
}
#endif
int main()
{
Demo d1(10.1, 100);double gie = 36.3;Demo d2 = gie + d1;
}
转换函数和友元
C++标准转换函数
编译时转换: reinterpret_cast、const_cast、static_cast
运行时候转换: dynamic_cast
!!慎用转换函数
异常:容错机制,错误处理机制
abort();//终止程序,当异常没有被捕获处理时,系统默认调用
//没有参数
当异常没有被捕获处理时,会先调用terminate()函数,这个函数默认调用abort(),可以更改terminate的调用方向
抛出异常,告诉调用者出现了什么异常
throw exception(字符串);
或 throw 字符串
//抛出异常后结束函数,去找try代码块,拦住异常,然后catch捕获(相应的catch来捕获)
set_terminate(函数):
这个函数用于设置terminate调用谁,需要传递一个没有返回值void,没有参数列表的函数名。
try{}:负责保护可能会出现异常的代码块,需要保护的放{}里
catch(exception &e){
cout<<e.what()<<endl;
}// catch捕获表示类型的异常
或catch(const char *p)
... //表示所有异常
栈退解:(释放栈区空间)
当try检查的函数不会产生异常时,但这个函数调用的另一个函数产生异常,当异常产生时,并不会把异常抛给调用函数,而是寻着调用往上找try关键字
exception:所有异常的父类
bad_alloc:请求分配内存失败时抛出的异常
自定义异常:
noexcept//
异常规范:
c++98标准 有throw()修饰函数不会产生异常
throw(“xxx”)告诉编译器可能会产生xxx这个异常
c++11标准 摒弃了throw()这个用法,使用了noexcept来代替其功能,表示不会产生异常
异常的继承:
首先要知道父类的类型和成员,才能初始化成员才能使用what()函数
class 自定义异常:继承方式 父类异常
例:
#include
using namespace std;
class Myexception{
public:
explicit Myexception(const char *what_arg):errmsg(what_arg){}
virtual ~Myexception(){}
const char *what() const noexcept;
private:
const char *errmsg;
};
const char *Myexception::what() const noexcept
{
return errmsg;
}
void func()
{
cout << "this is func()" << endl;
throw Myexception("it's test");
cout << "end of func()" << endl;
}
int main()
{
try{
func();
}
catch(const Myexception &mye){
cout << mye.what() << endl;
}
}
=========================================================================
智能指针:
是一个特殊的类模板,重载了“->”和“*”运算符,实现了c++的自动内存回收机制
指针对象释放,指针指向的空间也会被释放
四种智能指针:
shared_ptr, weak_ptr,auto_ptr,unique_ptr
头文件:#include
语法:
智能指针<指向数据的类型> (一个堆区空间)
auto_ptr:c++98使用,c++11摒弃的一种指针对象,和unique_ptr一样避免重复释放空间的策略:
当使用智能指针对象给另一个之智能指针对象赋值的时候,原指针对象对这片空间的所有权交给被赋值的对象,原指针对象变为指向无效数据(空指针 0地址)
auto_ptr:进行赋值操作后,会将空间也拿给被赋值者(这就是auto_ptr被摒弃的原因)
unique_ptr:和auto_ptr策略一样,只是如果出现重复指向的问题,auto_ptr能编译过但unque_ptr编译不会通过
shared_ptr(共享资源)
策略和unique_ptr不一样,允许多个指针对象指向同一片空间,用一个计数器来计数有多少个指针对象,当新加一个指针对象指向当前空间,计数器加一,当有一个指针对象销毁,计数器减一,计数器为0时才会释放堆区空间。
weak_ptr:“弱指针”,必须要配合shared_ptr一起用,他自己并不能单独创建并指向一个空间(要指向shared_ptr所指的空间),但是它不影响shared_ptr下面的use_count(),如果use_count变为0,但还有weak_ptr指向这片空间,还是会直接释放
成员函数
use_count():表示当前shared_ptr所指空间有多少个shared_ptr在共享
reset():释放指针对象所指向的堆区空间expired:判断weak_ptr所指向的空间是否释放,
注意:当需要多个指针指向多个空间时:用unique_ptr
当需要多个指针指向同一个空间时,用shared_ptr
=========================================================================
STL:标准模板库
{容器,迭代器,算法,空间配置器,配接器,仿函数,组件间的关系}
主要是一些“容器”的集合
容器:特殊的数据结构,实质是模板类
迭代器:一种复杂的指针,可以通过读写容器中的对象,实质是运算符重载
算法:读写容器对象的逻辑算法,实质是模板函数
头文件:
STL序列式容器:有严格的物理位置,固定了每个元素的顺序
1)vector:相当于数组
{
可以不指定大小,使用push_back(尾插)和pop_back(尾删)来进行动态操作
随机访问方便
支持[]操作符和vector.at()
vector<int> vec;//声明一个容器
for(int i=1;i<11;i++)//尾插操作
vec.push_back(i*10);
}</int>
2)list:双向循环列表,每个节点包括一个信息块,一个前驱指针,后驱指针,使用的是非连续的内存空间进行存储,相对于verctor占用多
不支持[]操作符和vector.at()
3)deque:双端队列或者双端数组
支持[]操作符和vector.at(),可在push,pop
STL关联式容器:没有严格的物理位置,可以排序(底层实现:二叉树)
set:排序容器,不允许有重复的元素出现
multiset:允许重复的值
map:由键值key-value来实现排序,不允许key重复
key:对应的value
multimap:允许key可以重复
STL迭代器:
iterator:遍历容器,但是不能查看容器内部结构
{
声明一个迭代器的语法:
vector<容器存储数据的类型>::iteator 自定义的迭代器名;
}
算法:for_each( ,,)
头文件:<algorithm>
第一个:迭代器
的二个:迭代器
第三个:需要进行操作的函数(将容器里的值作为参数)
将数据装入容器:
vector<int> vec; //定义一个存取int型数据的容器
for(int i = 1; i < 11; i++)
vec.push_back(i); </int></algorithm>
begin():头部位置
end():尾部后面一个的位置
遍历容器的三种方法:
1) vector<int>::iterator it; //声明一个迭代器(相当于一个指针)
for(it = vec.begin(); it != vec.end(); it++)
cout << *it << endl; </int>
2) for(int i = 0; i < 10; i++)
cout << vec[i] << endl;
3) void Print(int val)
{
cout<<val<<endl;
}
vector<int>::iterator itb;vector<int>::iterator ite;itb=vec.begin();ite=vec.end();for_each(itb,ite,Print);
区别:
vector:可以随机存取(随机迭代 it+n),读取数据很快,插入和删除很慢
list:如果要大量插入和删除,使用list会很快,但是随机访问很慢,向前或向后迭代deque:结合了vector和list的优点,缺点特别耗内存
一.sort函数
1.sort函数包含在头文件为#include<algorithm>的c++标准库中,调用标准库里的排序方法可以实现对数据的排序,但是sort函数是如何实现的,我们不用考虑!</algorithm>
2.sort函数的模板有三个参数:
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
(1)第一个参数first:是要排序的数组的起始地址。
(2)第二个参数last:是结束的地址(最后一个数据的后一个数据的地址)
(3)第三个参数comp是排序的方法:可以是从升序也可是降序。如果第三个参数不写,则默认的排序方法是从小到大排序。
3.实例
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 main() 5 { 6 //sort函数第三个参数采用默认从小到大 7 int a[]={45,12,34,77,90,11,2,4,5,55}; 8 sort(a,a+10); 9 for(int i=0;i<10;i++)10 cout<<a[i]<<" "; 11 } 
查看22道真题和解析