C++程序设计
第一章 语言基础
命名空间
namespace mycode
{
void fun(int x)
{
cout<<x<<" "<<"Hello world!"<<endl;
}
}
...
mycode::fun(5);
static_cast
double a=3.1415926;
int b=static_cast<int>(a);
访问全局/局部变量
int a=7;
inline void Case_Test()
{
double b=6.23451;
int a=static_cast<int>(b);//
mycode::fun(a);
mycode::fun(::a);
}
decltype类型推导
- [ ]
decltype
推测某个表达式的数据类型
double f();
decltype(f()) sum=0;//推导出sum为double
int i=42;
double d=3.14;
decltype(i+d) e;//推导出i+d类型为double类型
第二章 C++语言进阶
指针
指针变量的数据类型必须要一致,不同类型的不能赋值
```c++
int a[10];
int *p;
p=a;//或p=&a[0]; 数组的首地址
<pre><code class="line-numbers">### 指向数组的指针
```cpp
int (*p)[3];//p是指针变量,指向含3个int元素的一维数组
a[i][j]=*(*(a+i)+j)
int a=5;
int *pa=&a;
int **ppa=&pa;
a=5; <-> *pa=5; <-> **ppa=5;
示例:
char **p;
char *name[]={"hello","good","world","bye",""};
p=name;
while (**p!=NULL)
{
cout<<*p++<<endl;
}
/*
运行结果
hello
good
world
bye
*/
动态内存分配
栈区:局部数据
局部对象、函数形参、函数返回值存放在内存的栈区,随着函数的调用和参数的传递,在栈中分配内存,调用构造函数初始化对象;函数返回后,清除刚创建的局部数据,回收内存。所有操作由系统自动完成。
局部数据未初始化,则值为随机值。
堆区:局部变量
C++支持new
和delete
运算符,用于在堆中动态创建对象和释放对象。堆中的内存由程序员自由请求和分配,但必须由程序员负责使用delete
释放。
使用new
请求内存时,可能由于内存不足会造成失败,应该随时进行检测。
动态内存分配
int *p;
p=new int(5)//分配空间并初始化为5 *p=5
...
delete p;
多维的:
int *p;
p=new int[10];//分配10个int元素空间
*p=5;
*(p+1)=6//或者p[1]=6;
...
delete []p;
常指针和指向常量的指针
指向常量指针
char str1[]{"abcd"};
const char *pc=str1;
或char const *pc{str1};
pc所指向的字符串不能被修改。
pc[3]=‘a’; //错误
但pc可以指向别的字符串
char str2[]=“hello”;
pc=str2; //正确
常指针
char str1[]{"abcd"};
char * const pc{str1};
pc不能指向别的字符串,但可改变指针所指向的内容。
pc[2]=‘a’; //正确
char str2[]{“hello”};
pc=str2; //错误
指向常量的常指针
char str1[]{"abcd"};
const char * const pc=str1;
pc不能指向别的字符串,也不能改变指针所指向的内容。
pc[2]=‘a’; //错误
char str2[]=“hello”;
pc=str2; //错误
constexpr
常量表达式
const int max=20;//常量表达式
const int limit=max+1;//常量表达式
const int sz=getSize();//非常量表达式
constexpr变量:必须用常量表达式初始化
constexpr int mf=20;
constexpr int limit=mf+1;
constexpr int sz=getSize();//?
- [ ] 编译阶段确定初始值的常量可以声明为
constexpr
。 - [ ] 由编译器在编译阶段进行检查。
- [ ] 只有当
getSize()
是constexpr
函数时,才能通过编译
constexpr方法
constexpr int square(int x)//如果没有"constexpr"则会报错
{
return x*x;
}
double a[square(9)]; //-> double a[81];
空指针(nullptr)
旧版本中使用NULL
或者0
表示空指针,但存在一些隐含的问题
int *p=nullptr;
void f(int);
void f(char *);
f(0)//错误
f(nullptr);//强类型检查
指向函数的指针
int add(int a,int b){return a+b;}
int sub(int a,int b){return a-b;}
int compute(int a,int b,int (*pf)(int,int)){
return pf(a,b);
}
compute(3,5,add);
conpute(3,5,sub);
引用的概念
引用是变量或对象的别名,建立引用时必须确定引用的对象,对引用的操作实际上就是对被引用者的操作。
int i=1;
int &ri=i;
//以后对ri的操作,实际上操作的是i
//可以认为ri和i在内存中占用相同的单元
引用只是别名,并不为其分配存储单元
void sum(int &a,int b){
a+=b;
}
int main(){
int x(5),y(4);
sum(x,y);
cout<<x<<endl;
return 0;
}
$a$和$x$实质是同一个东西
函数返回指针
返回指向内存某处的指针
char* elem(char *s,int n){
return &s[n];
}
int main(){
char str[]="C++ Program";
char *pc=elem(str,5);
*pc='A';//将第五位的'P'改为'A'
cout<<str<<endl;
return 0;
}
//运行结果是 “C++ PAogram”
返回对某个变量的引用
char &elem(char *s,int n){
return s[n];
}
int main(){
char str[]="C++ Program";
elem(str,5)='A';
cout<<str<<endl;
return 0;
}
//运行结果是 "C++ PAogram"
不能返回指向局部变量的指针
char *elem(char *s,int n){
char c=s[n];
return &c;//c只是局部变量
}
char &elem(char *s,int n){
char c=s[n];//c只是局部变量
}
函数参数的默认值
int sum(int a,int b=2,int c=3){
return a+b+c;
}
string
string s(5,'a');//使用字符'a'重复5次构造s
vector
简介
vector<int> a(4);//4个元素,默认初始为0
vector<int> b(4);//1个元素,值为4
vector<int> c(10,-1);//10个元素,初始为-1
vector<int> d{2,3,5};//3个指定初始元素
迭代器遍历元素
vector<int>::iterator it;//迭代器循环不能用remove和push_back
数组的迭代器遍历
int a[]{2,3,6,1,9,0};
int *beg=begin(a);
int *end=end(a);
for (int *p=beg;p!=end;++p)
cout<<*p<<endl;
第三章 类和对象
在堆中创建对象
int main(){
Fraction *pFraction1=new Fraction;
pFraction1->setNum(8);
(*pFraction1).setDen(14);
pFraction1->output();
delete pFraction1;
}
注意这里的
*
和->
的用法使用
new
记得delete
this作用
void Fraction::setNum(int num){
this->num=num;
}
default
在构造函数中如果没有自己定义的那么系统会提供一个没有实质功能的默认构造函数也就是Student() {}
。如果我们写出了构造函数,那么这个默认的就还需自己写
class Student{
public:
Student(int _age):age(_age){}//写出这个之后无默认构造函数,需要写下一行
Student()=default;//生成默认构造函数
Student(){}//与上一行同样效果
//下面是default的拷贝构造
Student(Student &x){age=x.age;h=x.h;}
Student(Student &x)=default;//与上一行是一样的
private:
int age;
}
但在拷贝构造里面也可以用default
(见上代码)
类内初始化
C++11中也可以在类内定义初始值
class Student
{
...
private:
int age=1,h=2;
}
委托构造函数
class Student
{
public:
Student(int _age,int _h):age(_age),h(_h){cout<<"GZ"<<endl;}
Student(int x):Student(x,7){cout<<"WT"<<endl;}
private:
int age,h;
}
/*
输出:
GZ
WT
*/
委托构造时,先调用委托的版本构造函数,再调用构造函数自身。
类型转换构造函数
//class Student同上
int main()
{
Student Karry;
Karry=5;//会调用class中Student(int x) 构造函数构造出一个临时的
/* 等同于:
Student temp=Student(5);
c=temp;
*/
}
拒绝隐式转换——explicit
explicit Student(int x):Student(x,7){cout<<"WT"<<endl;}
这样程序就会报错,Karry=5
无法执行
explicit要求显示调用这种类型转换
Karry=Student(5);
或者是Karry=static_cast<Student>(5);
拷贝构造函数
用途:建立新对象时,用一个已经存在的同类型对象去初始化这个新对象
每个类必须拥有一个,可以自定义,若未定义的话系统会自动生成缺省的拷贝构造,用于复制数据成员完全相同的新对象
class Student{
public:
Student(const Student& t)
{
age=t.age;h=t.h;
cout<<"ZDY"<<endl;//Karry=Carry;这时候会调用拷贝构造
}
...
}
一下四种都会调用拷贝构造函数
Student Karry=Carry;
Student Karry=Student(Carry);
Student Karry(Carry);
Student Karry{Carry};
对象作为函数返回值
Student Copy(Student f){//f也就是第三次,创建了f
Student f1; //第4次:定义对象
f1=f;
return f1; //第5次:返回临时对象temp
}
int main(){
Student f2(4); //第1次:定义对象
Student f3; //第2次:定义对象
f3=Copy(f2); //第3次:参数传递
//f3=temp;
f3.print();
}
对象的初始化
Student f2=...
这个叫初始化;Student f2;f2=
这个叫赋值(需要重载=
)
阻止拷贝构造
Student(const Student& t)=delete;
若无,程序会自动产生拷贝构造
default
:强制生成缺省版本函数(构造,析构,拷贝,赋值运算符)
delete
:禁止生产缺省版本函数
Array1D类分析
class Array1D{
...
private:
int *pData;
int size;
}
构造函数-动态分配内存
Array1D::Array1D(int *p,int s){
pData=new int[s];
size=s;
for(int i=0;i<size;++i) pData[i]=p[i];
}
Array1D::Array1D(int s){
pData=new int[s];
size=s;
for (int i=0;i<size;++i) pData[i]=0;
}
析构函数
Array1D::~Array1D(){
delete[] pData;
}
右值引用
C++中的引用必须绑定到1个左值,无法定义1个常量、表达式的引用
void fun(int& a);
int b=2,c=4;
fun(b);//正确
fun(b+c);//错误,将b+c传入&a中??
函数参数修改为
const int&
后可以传递,无法修改C++11增加
&&
可以引用临时的右值void fun(int &&a); fun(b+c);
增加右值引用喝Move语义
class Array1D{
public:
Array1D(const Array1D& a); //拷贝构造
Array1D(Array1D&& a); //移动构造
Array1D& operator=(const Array1D& a); //拷贝赋值
Array1D& operator=(Array1D&& a); //移动赋值
...
};
如果要增加右值引用和move语义,在原来正常拷贝构造与拷贝赋值的基础上,增加移动构造和移动赋值方法。
移动构造函数实现
Array1D::Array1D(Array1D &&a){
size=a.size;
pData=a.pData;
a.size=0;
a.pData=nullptr;//delete?
//移动时直接接管参数对象的数据,然后将其置空
}
普通拷贝Array1D b(a)
需要内存分配
Array1D ff(){
Array1D temp(100);
return temp;
}
Array1D c=ff();//内部数据移动,初始化构造c对象,不用为c分配内存
强制移动的示例
Array1D a(100); Array1D b(move(a));
通过move操作,强制a移动并移动构造对象b 移动后,a无效了,以及被移走
静态数据成员
class Student{
public:
static int cnt;//声明
Student(){cnt++;}
~Student(){cnt--;}
...
};
int Student::cnt=0;//定义并初始化
友元
class Point{
private:
double x,y;
public:
Point(double xx=0, double yy=0):x(xx),y(yy){}
double getX() { return x; }
double getY() { return y; }
friend double getDis(const Point& a,const Point& b);
//若不用友元则不能直接用
};
double getDis( const Point &a, const Point &b){
double dx,dy;
dx=b.getX()-a.getX();//不用友元则不能直接访问b.x等
dy=b.getY()-a.getY();
//用友元则可以 dx=b.x-a.x;dy=b.y-a.y;
return sqrt(dx*dx+dy*dy);
}
getDis
只是类外部定义的普通全局函数,被声明为Point类
的友元函数。不能在函数名前加
Point::前缀
成员函数版本
double Point::getDis( const Point &a){
double dx,dy;
dx=x-a.x;
dy=y-a.y;
return sqrt(dx*dx+dy*dy);
}
...
cout<<"distance:"<<p1.getDis(p2)<<endl;
常成员函数(*)
class Array1D{
public:
...
int getSize()const;
int getValue(int index) const;
void setValue(int index, int value);
private:
...
};
const Array1D a1(…);
Array1D a2(…);
a1.getValue(index);//正确
a1.setValue(index, value);//错误
const Array1D *pArray1=&a2;
pArray1->setValue(index, value);//错误
Array1D * const pArray2=&a2;
pArray2->setValue(index, value);//正确
指向常量的指针,可以修改其所指向对象的内容
第四章 运算符重载
成员函数重载+
class Fraction{
private:
int num,den;
public:
...
Fraction operator+(const Fraction& f) const{
return Fraction(num*f.den+den*f.num,den*f.den);
}
}
...
Fraction a(1,4),b(1,3),c;
c=a+b;//c=a.operator+(b);
operator+
相当于函数名
友元函数重载-
class Fraction{
...
friend Fraction operator-(const Fraction& a,const Fraction& b);
}
Fraction operator-(const Fraction& a,const Fraction& b){
return Fraction(a.num*b.den-a.den*b.num,a.den*b.den);
}
...
c=a-b;
Fraction a,b a-b
返回的是一个Fraction
重载赋值运算符
```C++
class Fraction{
...
void operator=(const Fraction& f);
}
void Fraction::operator=(const Fraction& f){
num=f.num;
den=f.den;
}
...
c=a;//如果是c=b=a呢?
> 如果是`c=b=a`则会出现语法错误,`=`运算符右结合先执行`b=a`,返回值为`void`无法赋值给`a`
#### 问题的解决1
```C++
class Fraction{
...
Fraction operator=(const Fraction& f);
};
Fraction Fraction::operator=(const Fraction& f){//返回一个Fraction
num=f.num;den=f.den;
return Fraction(num,den);//效率太低
}
效率太低,我们选择传引用
问题的解决2
class Fraction{
...
Fraction& operator=(const Fraction& f);
};
Fraction& Fraction::operator=(const Fraction& f){//返回引用
num=f.num;den=f.den;
return *this;//效率高
}
c=b=a;
先执行b=a
,b
被赋值后返回b
的引用,再赋值给c
,最后的返回值丢弃
重载+=
运算符
class Fraction{
...
Fraction& operator+=(const Fraction& f);
}
Fraction& Fraction::operator+=(const Fraction& f){
num=num*f.den+f.num*den;den=den*f.den;
normalize();
return *this;
}
Fraction f1(3,4),f2(2,3);
f1+=f2;
-
(负号)运算符
class Fraction{
...
Fraction operator-()const;
}
Fraction Fraction::operator-()const{
return Fraction(-num,den);
}
...
c=-b;
错误示范
Fraction Fraction::operator-(const Fraction& f){
num=-num;
return *this;
}
这个时候改变了传入参量的值,实际上不应该让它改变
友元函数版本
class Fraction{
...
friend Fraction operator-(const Fraction& );
}
Fraction operator-(const Fraction& f){
return Fraction(-f.num,f.den);
}
c=-b;
分数与实数类型转换
class Fraction{
...
Fraction(int n):Fraction(n,1){}
}
Fraction a(3,4),b,c;
b=a+2;//先将2转化为Fraction,然后与a相加赋值给b
//c=2+a; 错误,a不能转换为整数
//friend Fraction operator+(const Fraction& ,const Fraction& );
前置、后置++
++
和--
运算符也可以重载,但为了区分前置和后置运算。C++约定把前置运算符重载为单目运算符函数,即表达式++a
;解释为a.operator++()
把后置运算符看成双目运算符,在参数表内放置一个整型参数,该参数没有任何作用,只是用来作为后置运算符的标识。
a++
;解释为a.operator++(int)
class Fraction{
...
Fraction& operator++();
Fraction operator++(int);
}
//前置 ++a
Fraction& Fraction::operator++(){
num+=den;
return *this;//传递引用 *this 就是当前的 改变
}
//后置 a++
Fraction Fraction::operator++(int a){
Fraction f(*this);//++之前的给f
num+=den;
return f;//返回的是f(++之前未改变)
}
int main(){
Fraction f(3,4);
(f++).output();
(++f).output();
return 0;
}
友元函数实现
class Fraction{
friend Fraction& operator++(Fraction& f);
friend Fraction operator++(Fraction& f,int)
}
Fraction& operator++(Fraction& f){
f.num+=f.den;//先修改f
return f;//再返回f引用
}
Fraction operator++(Fraction& f,int a){
Fraction temp(f);
f.num+=f.den;
return temp;
}
int main(){
Fraction f(3,4);
(f++).output();
(++f).output();
return 0;
}
我不理解为什么重载
++
的友元函数时候需要传入参量Fraction& f
类型转换运算符
class Fraction{
...
operator double() const{
return getValue;
}
}
int main(){
Fraction f(3,4);
cout<<3.5+f<<endl;//若重载+则会有二义性
}
若声明explicit operator double() const{return getValue();}
则需要cout<<3.5+double(f)<<endl; cout<<3.5+static_cast<double>(f)<<endl
完善Array1D
重载赋值运算符
Array1D& Array1D::operator=(const Array1D& a){
pData=a.pData;
size=a.size;
}
如果类中包含指针成员,缺省赋值运算符直接在,指针之间赋值,导致内存问题。
Array1D& Array1D::operator=(const Array1D& a)
{
if(this==&a) return *this;//判断是否自我赋值
delete[] pData;//先释放当前内存
copyData(a.pData, a.getSize());
return *this;
}
增加右值引用和Move语义
class Array1D{
public:
Array1D(const Array1D& a);//拷贝构造
Array1D(Array1D&& a);//移动构造
Array1D& operator=(const Array1D& a);//拷贝构造
Array1D& operator=(Array1D&& a);//移动构造
}
移动赋值运算符
Array1D& Array1D::operator=(Array1D &&a)
{
if(this==&a) return *this;
delete []pData;
size=a.size;
pData=a.pData;
a.size=0;
a.pData=null;//a清空
}
重载下标运算符
class Array1D{
...
int& operator[](int i){return pData[i];}
const int& operator[](int i)const{return pData[i];}
}
...
array2[2]=10;
Array2D
动态内存分配
//申请内存
int **p;
p=new int*[row];
for (int i=0;i<row;++i)
p[i]=new int[col];
//释放内存
for (int i=0;i<row;++i)
delete[] p[i];
delete[] p;
class Array2D{
public:
Array2D(int row);
~Array2D();
Array1D& operator[](int index);//返回的是1维
private:
Array1D *pData;//很多1维
int row;
}
Array2D::Array2D(int r):row(r){
pData=new Array1D[row];//调用缺省构造函数
}
Array2D::~Array2D(){
delete[] pData;
}
Array1D& Array2D::operator[](int index){
return pData[index];
}
int main()
{
Array2D a(3);
a[0]=Array1D(...);
a[2][1]=5;//可以这样 两次[]
}
第五章 继承与多态
派生类对基类的扩充
派生类继承了基类除构造函数、析构函数以外所有的数据成员和成员函数,实现了代码重用。
扩充:在派生类中增加新的成员函数和数据成员。
改造:当继承而来的成员不能满足需要时,可以进行覆盖。覆盖是在派生类中定义与基类同名的函数,覆盖也可针对数据成员。慎用!
区分重载:在同一个类中定义同名函数但参数不同。
继承方式
public
protected
private
1.公有继承:public
基类 | 派生类 | 派生类中 | 通过派生类对象 |
---|---|---|---|
public |
public |
可以访问 | 可以访问 |
protected |
protected |
可以访问 | 不可访问 |
private |
不可访问 | 不可访问 | 不可访问 |
举例:
class Point{
double x;//private
protected:
double y;//protected
public:
...
}
class Circle:public Point{
double radius;
public:
Circle(...)//构造
void setXYR(double a,double b,double r){
x=a;//错误。不可访问基类私有成员,但能访问保护成员
//setX(a) 正确。通过基类的公有成员函数间接访问
y=b;
radius=r;
}
}
小结
2.保护继承:protected
基类的私有成员在派生类中是不可访问的,而公有和保护成员成为派生类的保护成员。
通过派生类的对象不能访问基类的任何成员,需要在派生类中定义公有接口
class Point{
double x;
protected:
double y;
public:
...
}
class Circle:protected Point{
double radius;
public:
void setXYR(double a,double b,double r){
x=a;//错误。不可访问基类私有成员
setX(a);// 可以通过基类的公有成员函数间接访问
y=b;//正确。但能访问保护成员
radius=r;
}
}
class Test:protected Circle{
public:
void setXYRC(double a,double b,double r,int c){
setX(a);//正确
y=b;//正确 它们都相当于Test的保护成员,可以访问
...
}
}
与上图不同的是,派生类B对象不能调用基类A的公有成员,因为它已经变成了保护的了
3.私有继承:private
基类的私有成员在派生类中是不可访问的,而公有和保护成员成为派生类的私有成员
通过派生类的对象不能访问基类的任何成员
构造与析构
构造函数调用顺序
创建派生类对象时,A→B(A派生B)。
1.调用A中对象成员(如果有)对应的构造函数
2.调用基类A构造函数
3.调用B中对象成员(如果有)对应的构造函数
4.调用派生类B的构造函数
原则:
1.父子类之间,先基类构造,再派生类构造
2.同一个类中,先对象成员,再本类构造函数体
析构函数调用顺序
创建派生类对象时A→B(A派生B)。
1.调用派生类B的析构函数
2.调用B中对象成员(如果有)对应的析构函数
3.调用基类A析构函数
4.调用A中对象成员(如果有)对应的析构函数
示例
class Point
{
double x,y;
public:
Point(){ x=0; y=0; }
Point(double a,double b) { x=a; y=b; }
…
};
class Circle : public Point
{
private:
double radius;
Point p;
public:
Circle(double a,double b,double aa,double bb,double r);
…
};
Circle::Circle(double a,double b,double aa,double bb,double r):Point(a,b),p(aa,bb)
{
radius=r;
}
Circle c(3,4,5,6,8);
//调用顺序:基类→p→本类构造函数
//先Point(3,4),再p(5,6)...
在继承过程中,构造函数和析构函数不能被继承
禁止继承 final
class Point final{
}
多继承
举例:
class RoundTable:public Table,public Circle{
...
}
说明:
在多继承派生类的构造函数中,要通过初始化列表的形式调用直接基类的构造函数。
构造函数的执行顺序:先执行基类构造函数,再执行派生类构造函数;多个基类构造函数按照定义派生类时的顺序,与初始化列表中的顺序无关。
使用多继承容易造成混乱,应避免使用。
赋值兼容性规则
每一个派生类的对象都是基类的一个对象。赋值兼容性规则是指在公有派生情况下,一个公有派生类的对象可以当作基类的对象使用,反之则禁止
- 派生类的对象可以赋值给基类对象。
- 派生类的对象可以初始化基类的引用。
- 指向基类的指针也可以指向派生类。
- 通过基类对象名、指针只能使用从基类继承的成员
派生类的对象可以赋值给基类对象
Circle c(2,3,4);
Point p;
p=c;//通过p不能访问或间接访问radius成员
Point p(2,3);
Circle c;
c=p;//错误
派生类的对象可以初始化基类的引用
Circle c(2,3,4);
Point &rp=c;//rp虽然是c的引用,但是只能访问基类部分数据和方法
派生类对象地址赋值基类指针
Circle c(2,3,4);
Point *pp=&c; //基类指针可指向派生类对象
类型转换:dynamic_cast
Circle c(2,3,4);
Point p(2,3);
Point *pp=&c; //基类指针可指向派生类对象
Circle *pc=dynamic_cast<Circle *>(pp);
Circle *ppc=dynamic_cast<Circle *>(&p);
dynamic_cast
是一种运行时类型转换,可以转换指针或引用,用于继承体系中的类型转换。若转换失败,返回空指针或抛出异常(引用)。
static_cast
在pcc
时候会通过编译,但是运行时会出现崩溃,不安全
覆盖技术
在派生类中定义与基类同名的成员函数后,会出现覆盖现象;实现重新定义基类成员函数。
子类的函数覆盖了基类的所有版本同名函数
父子类之间,同名函数,参数签名不同,不会形成重载
虚函数
class A
{
public:
void shout(){
cout<<"I AM A!"<<endl;
}
};
class B:public A
{
public:
void shout(){
cout<<"I AM B!"<<endl;
}
};
int main()
{
B b;
A *a=&b;
a->shout();
system("pause");
return 0;
}
答案结果:I AM A!
class A
{
public:
virtual void shout(){
cout<<"I AM A!"<<endl;
}
};
class B:public A
{
public:
virtual void shout(){
cout<<"I AM B!"<<endl;
}
};
int main()
{
B b;
A *a=&b;
a->shout();
system("pause");
return 0;
}
答案结果:I AM B!
虚析构函数
C++中规定,某个类若有虚函数,则应该将其析构函数设置为虚函数,否则容易出现内存泄漏等问题
class Shape
{
double x,y;
public:
virtual ~Shape() {……}//虚析构函数
virtual double getArea() {return 0; }
};
派生类也是需要加上virtual
纯虚函数
virtual double getArea()=0;
//纯虚函数只有定义没有实现
基于引用的多态
class A{
public:
virtual void shout(){
cout<<"I AM A!"<<endl;
}
};
class B:public A{
public:
virtual void shout(){
cout<<"I AM B!"<<endl;
}
};
class C:public A{
public:
virtual void shout(){
cout<<"I AM C!"<<endl;
}
};
void shout(A &id){
id.shout();
}
int main()
{
B b;C c;
shout(b);
shout(c);
system("pause");
return 0;
}
答案结果:
I AM B!
I AM C!
第六章 模板泛型编程基础
定义函数模板
template <typename T>
T max(T a,T b){
return a>b?a:b;
}
模板参数的演绎
template <typename T>
T max(T a,T b){
return a>b?a:b;
}
max(4,7);//编译器能推断出T为int
max(4,4.2);//出现二义性,4是int,4.2是double,无法确定T的类型,见下
函数后置类型声明
max<int> (4,7);
max<double>(4,4.2);//可能出现二义性情况下,显示指定模板参数类型
类型推导
template <typename T1, typename T2, typename RT>
RT sum(T1 a, T2 b)
{
return a+b;
}
template <typename T1, typename T2>
auto sum(T1 a, T2 b) -> decltype(a+b)//可推导
{
return a+b;
}
类模板
类模板的定义
template <typename T>//标志模板的开始,同时指定模板中使用的类型参数
class Exam
{
public:
void setValue (T const& value);
T getValue() const;
private:
T elems;
};
Exam
并不是一个真正的类,只是一个类模板(生成类定义的骨架),类中数据成员和部分成员函数参数的类型还没有确定,并以模板并没有定义任何类。
类模板成员函数的定义
template <typename T>//每个成员函数都得写
void Exam<T> ::setValue(const T& value)
{
elems = vlaue;
}
template <typename T>
T Exam<T> ::getValue() const
{
return elems;
}
成员函数实质上函数模板
Exam
不是真正的类,真正的类是按照T类型实例化后的类,名称为Exam<T>
每个成员函数之前都要用模板标志,哪怕不使用类型参数
编译器生成的类实例
class Exam<int>
{
public:
void setVal (int const&);
int getVal() const;
private:
int elems;
};
void Exam<int>:: setVal(const int& e)
{ elems=e; }
int Exam<int>:: getVal() const
{ return elems; }
//----------------------
class Exam<double>
{
public:
void setVal (double const&);
double getVal() const;
private:
double elems;
};
void Exam<double>:: setVal(const double& e)
{ elems=e; }
double Exam<double>:: getVal() const
{ return elems; }
第七章 STL容器与迭代器
迭代器方法
迭代器类型
iterator
:可用于读写
const_iterator
:只读访问
相关方法
begin()
:返回引用第一个元素的迭代器
end()
:返回引用最后一个元素下一个位置的迭代器
元素可用[begin(),end())
左闭右开区间表示
rbegin()
和rend()
方法返回逆向迭代器区间
cbegin()
和cend()
方法返回常量迭代器(C++11)
vector
构造与赋值
vector<double> a;//empty vector
vector<double> b(10);//10个元素
vector<double> c{10};//1个元素,初始值为10
vector<double> d(10,0);//10个0
vector<double> e{10,0.5};//两个元素,10和0.5
e.assign(5,100);//重置为5个100
{}
表示内容,()
是构造
迭代器
vector<double>::iterator it=vec.begin();//auto也可
for (;it!=vec.end();++it){}
遍历
for (auto& i:vec)
遍历获得元素引用,可以修改
for (auto i:vec)
遍历获得元素拷贝,只读元素
访问其他方法
at(int index)
检查下标,如果越界的话,抛出out_of_range
异常
[]
运算符不进行越界检查
添加
vectorOne.insert(vectorOne.begin() + 3, 4);
//insert版本1:在指定迭代器位置前插入一个元素
vectorOne.insert(vectorOne.end(),vectorTwo.begin(),vectorTwo.end());
//insert版本2:第一个参数插入位置,第二和第三个参数指定插入数据范围
vectorTwo.insert(vectorTwo.begin(), 10, 100);
//insert版本3:在指定位置前插入10个100 erase同
预留容量
vector<int> intVector;
intVector.reserve(1000);//预留指定大小的空间
//resize()方法:改变元素数量,若多出元素,缺省构造
//容器的容量扩展到1000,实际元素个数为0
//在容器内元素数量增加到1000之前,不会从新分配内存
map
map的迭代
for (map<char, int>::iterator it = scoreMap.begin();it != scoreMap.end(); ++it)
find
auto it=scoreMap.find('B');
if (it!=scoreMap.end()) ...//不等于最后=找到
第八章 STL算法
find
vector<int> v;
vector<int>::iterator it = find(v.begin(),v.end(),88);
if (it==v.end()) ...//未找到
find_if
find_if
算法的前2个参数指定迭代器范围,第3个参数不直接指定要查找的值,而是提供一个用于匹配的判定式,可以是函数指针、函数对象或Lambda
表达式
find_if
对迭代器区间的每个元素调用判定式,当返回true
时,表示找到匹配的元素。
bool perfectScore(int num){
return (num >= 100);
}
vector<int>::iterator it = find_if(v.begin(),v.end(),perfectScore);
accumulate
accumulate1
double sum = accumulate(nums.begin(),nums.end(),0);
第三个参数表示开始累加的初始值
accumulate2
int product(int num1, int num2){
return (num1 * num2);
}
double mult = accumulate(nums.begin(),nums.end(),1,product);
第四个参数指定具体累加方式,此处是乘积
stl函数对象
5个算术类函数对象类plus
,minus
,multiplies
,divides
,modulus
plus<int> myPlus;
int res = myPlus(4, 5);
double mult=accumulate(nums.begin(),nums.end(),1,multiplies<int>));
6个比较类函数对象类equal_to
、not_equal_to
、less
、greater
、less_equal
、greater_equal
Lambda表达式示例
int main()
{
vector<int> vec{1,3,5,2,6,9};
int value=3;
int cnt=count_if(vec.begin(),vec.end(),[=](int i){return i>value;});
cout<<cnt<<endl;
return 0;
}
遍历容器,统计大于3的元素个数
前两个参数指定区间,第三个参数通过
Lambda
表达式指定规则,i为遍历过程中传入的当前元素拷贝
int main()
{
vector<int> vec(10);
int value=1;
generate(vec.begin(),vec.end(),[&value],{value*=2;return value;});
cout<<cnt;
}
generate
算法实现按照"2 4 8..."填充容器
vector<int> v;
vector<int>::iterator it=find_if(v.begin(),v.end(),[](int i){return i>=100;});
查找第一个大于100的元素
查找算法示例
vector<int> v{0,0,1,0,2,9};
auto begin=v.begin();
auto end=v.end();
auto it=find_if_not(begin,end,[](int i){return i==0;});
if (all_of(begin,ans,[](int i){return i==0;})){...}
find_if_not
查找第一个不为0
的元素
all_of
算法判断是否所有元素均为0
for_each示例
void printPair(const pair<int,int>& elem){
cout<<elem.first<<"->"<<elem.second<<endl;
}
...
map<int,int> mp;
for_each(mp.begin(),mp.end(),&printPair);
遍历
mp
中每个元素(pair
),自定义处理函数printPair
用Lambda表达式
for_each(mp.begin(),mp.end(),[](const pair<int,int>& p){cout<<p.first<<"->"<<p.second<<endl;});
修改类算法
transform示例
vector<int> v;
...
transform(v.begin(),v.end(),v.begin(),[](int i){return i+100;});
前两个参数指定区间,第三个参数指定起始位置,Lambda通过传入的i,遍历区间对每一个元素+100后返回写入目标区间
copy&move
copy(v1.begin(),v1.end(),v2.begin());
move(v1.begin(),v1.end(),v2.begin());
replace_if
replace_if(v.begin(),v.end(),[](int i){return i<0},0);
replace_if(v.begin(),v.end(),[](int i){return i>100;},100);
for_each(v.begin(),v.end(),[](int i){cout<<i<<endl;});
replace_if
:前两个参数指定区间,Lambda
指定匹配规则,即小于0的,最后一个参数指定替换的新值
remove_if
auto it=remove_if(strings.begin(),strings.end(),[](const string& s){return s.empty();});
strings.erase(it,strings.end());
排序类算法
merge()示例
merge(v1.begin(),v1.end(),v2.begin(),v2.end(),vecMerged.begin());
merge
:前四个参数代表两个区间,第五个参数指定目标区间起始位置
文章评论