c++

思维导图:

对于思维导图中笔记部分因为无法显示,故导出了文本文件(导出word导出不全,只有前5页)

思维导图的完整文件在博客园的文件中的cplusplus中。

# C++

## 基础部分

### c++与c一些不同点

- c++中定义常量用const而不要用define即从#define pi 3.14替换为const float pi=3.14
- c++支持重载,而c不支持重载
- c++支持带默认的形参,而c不支持带默认的形参

### c++和c一些相同点

- c++的作用域和生存周期部分和c相同
- c++的数组和指针和c一样

### 对于系统给的头文件:#include<iostream> 对于自己写的头文件#include "people.h"

### c++有string类,头文件为#include <string>

### 给变量赋初值: int a=3 或者int a(3)

### &在执行语句中,表示取得其后变量的存储地址值,如:p=&a; &在声明语句时,表示定义一个引用,如:int &a=c

### 动态内存分配

- 动态内存分配使用new和delete

int *a=new int(2);//动态分配空间,用2初始化(构造函数的参数列表),并返回int型指针
int *b=new int();//动态分配空间,用0初始化(用户定义的默认构造函数进行初始化,若无,则用系统生成的默认构造函数初始化,并将其基本类型和指针类型的成员初始化为0),并返回int型指针
int *c=new int;//动态分配空间,用0初始化(用户定义的默认构造函数进行初始化,若无,则用系统生成的默认构造函数初始化),并返回int型指针

delete a;

//动态分配数组
int *p=new int[10];//10可以为自己输入的一个变量
int *p=new int[10]();//括号选带,与前面的一样
delete []p;

//多维:
float (*cp)[25][10];
cp=new float[10][25][10];//此时,cp可以向三维数组名一样使用

//数组指针和指针数组
int *a[3]//a是一个整形指针的数组,a[0] a[1] a[2]均是整形指针
int (*a)[3]//a是一个指针,指向一个3维列向量

- 用new动态分配空间后,new 返回的是一个指针,指向分配空间的首地址

shape *a=new Recangle(1,2);
Shape *a[2]={new Rectangle(1,2),new Circle(2)};
int *p=new int(2);
int *p=new int[4];

- 对指针用new 分配内存后要记得用delete释放掉。对于分配的单个变量用delete p,对于分配的变量数组用delete [] p;

p的区别在于他们对于非内部数据对象处理上,如果数组类型是自定义类,那么new[]只能用delete[]来对应,new和delete对应,但是对于普通数据类型而言,他们作用的效果是一样的,例如int* p=new int[10],delete p和delete[] p作用效果是一样的,原因是内部普通数据类型没有析构函数。

### 指针

- 函数指针:int (* p_fn1)(int);
- 成员函数指针:long ( A::*p_fn2)(int);
- 指向类的静态成员的指针: int *ptr=&point::count
- 指向类的非静态成员的指针:int point::*ptr=&point::x
- 给三维数组分配空间的指针:float (*cp)[25][10]; cp=new float[10][25][10]

### 指针和引用:指针是地址,引用是别名,都能对原有变量做出改变。int a; int *p=&a; int &c=a;

### 并不是总是可以返回引用的,函数不能返回在函数中创建的临时对象的引用,因为当函数结束时,临时对象将消失,因此这种引用是非法的,在这种情况下,应返回对象,以生成一个调用程序可以使用的副本。通用的规则是,如果函数返回在函数中创建的临时对象,则不要使用引用。如果函数返回的是通过引用或指针传递给它的对象,则应当按引用返回对象。

### while退出循环是看其数值是否为0

NULL在数值上为0 0是进不入while循环的 
 ‘0’是可以一直进while循环的,因为字符'0'其ASCII码对应的数学值不是0
‘’是NULL,也是进不入while循环的。 ‘ ‘空格的ASCII码值为32,能进while循环


int ,float double =NULL 代表其值为0
指针=NULL 代表其值为0(保存的地址是0)
类A a=NULL 代表其不能用,编译无法通过。
引用=NULL 代表其值不能用,编译无法通过

### String(const char *str=NULL);则判断为:if(str==NULL) //不是*str==NULL

### const的使用

const int a;//类中的常数据成员,其初始化只能在构造函数的初始化列表中完成
//区别:
static const int b;//静态常数据成员,其初始化在其cpp文件最前面
const int A::b=10;//如果不是const,则去掉const
void print() const;//常成员函数,不能改变类中的数据成员
const A &p;//常引用,在函数传参时用,其对象不能被更新
const A* p;//常指针,不能通过指针p对A类对象改变,但是此对象可变(通过该对象或其他指针),并且p可变。
A * const p;//p指针内的地址不能变,但是p指向的对象可变。

### 输入和输出

- 字符串输入问题

//字符串输入,键盘输入 123 ABC
string s1;
cin>>s1;//s1="123"
getline(cin,s1)//s1="123 ABC"
getline(cin,s1,',')//以 ','为分隔符读取字符串

- scanf输入字符串不能有空格,否则会当作结束标志,而用gets输入可以解决问题
- gets用时,如果前面有输入,则也需要用getchar清除回车符号

### 异常处理

- 通过try catch 和throw共同完成处理异常。try中有可能异常的因素,捕获后用throw抛出,然后catch接受后进行处理

/*
 * main.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include <iostream>
#include <stdio.h>
using namespace std;


int main()
{
char* a=new char[10];
int b;
try{
cin>>a;
sscanf(a,"%d",&b);
if(b<0||b>1){
throw b;
}

}catch(int x){
cout<<"x="<<x<<"请输入0/1"<<endl;

}
cout<<"已经执行"<<endl;
delete []a;
return 0;
}

- 在执行异常处理程序段之前,程序首先将try子句的保护程序段中已经构造但还未析构的局部Test类的对象析构

### 函数

- 传递方式

- 值传递

- 特点:不会真正改变传入参数的值
- 用法

/*值传递*/
int a,b,c;
int add(int x,int y)//在此函数内如果对传入x和y进行变化,a和b并不变
c=add(a,b)

- 引用传递

- 特点:引用是别名,就是变量本身,会改变传入参数的值,类似于c语言中用指针传递
- 用法

/*引用传递*/
int a,b;
void swap(int &x,int &y);//在此函数内如果对传入的x和y进行变化,a和b会变。
swap(a,b);

- 虽然用引用是为了能改变传入参数。但是因为用引用传参,不需要用拷贝构造函数新建变量,速度快,所以对于类的对象,如果需要传参,有时也用引用传递。但要注意的是,如果用引用传递时,传入参数不会变化,则最好加上const,防止编译器报错。原因:如果传入的是临时变量的引用则编译器会报错 改正方法:加上const

- 指针传递

- c语言中想改变传入变量时的写法,c++兼容,依旧可用,但不如引用传递方便
- 用法

/*区别下c中想改变时用的指针传递*/
int a,b;
void swap(int *x,int *y);
swap(&a,&b);//会发现在此处进行调用时,c++写起来更加方便,

- 特别注意的是上述三个方式是相对于单个变量讲的。而对于数组,因为传的是地址,所以会对传入的变量进行改变

- 内联函数

- 作用:不像别的函数一样,控制转移,而是在编译期间直接将代码覆盖过去,适合简单的,没循环的函数
- 用法:使用方法为在函数前加一个inline 如: inline int add(int x,int y);

好像无论是写到声明处还是实现处,还是都写都不会报错,所以统一写到声明处吧。
  好像加不加inline和是不是内联函数没关系,编译器不保证给你弄成内联,只是你有一个请求,具体看情况。可能简单函数没有inline也是内联的,复杂函数有inline也不是内联的。

- 对于类的成员函数,只要实现写在类内,就相当于声明为内联了。写在类外,如果想是内联,则需要加inline
- 唯一个特殊,推荐inline写在实现部分,而不是声明部分

- 带默认形参值的函数

- 用法如: int add(int x=5,int y=4);
- 有默认值的形参必须在形参列表的最后
- 最好是函数声明时,写出默认形参,函数定义时不写(因为相同定义域内,默认形参只能写一次,要在定义和声明选一个写,就统一写到声明处吧)

- 函数重载

- 用法如: int add(int x,int y); float add(float x,folat y);//注意必须函数同名,然后参数列表不同(即参数列表顺序不同或者类型不同)
- 原理:编译时前两个会编译为add_int_int add_float_float,所以只要让其编译后名字不一样,就是重载,名字一样就报错

## 类

### 作用:将一些现实中的事物,以类别区分,将其属性和行为同时存储下来。如教师,其属性包括姓名,教师编号,行为包括 教学等

### 构成及使用(以类Teacher为例)

/*
 * Teacher.h
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */

#ifndef TEACHER_H_ //防止该头文件被重复加入
#define TEACHER_H_ //防止该头文件被重复加入
#include <iostream>
using namespace std;
class Teacher{
public:
Teacher(); //构造函数
Teacher(string _name,string _id); //重载的构造函数
Teacher(Teacher &teacher); //拷贝构造函数
~Teacher();         //析构函数
void teacherStudent();   //自己定义的其他函数
private:
string name;       //类的私有成员
string id;
};

#endif /* TEACHER_H_ */  //防止该头文件被重复加入


/*
 * Teacher.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include "Teacher.h"

Teacher::Teacher(){
cout<<"Teacher的无参构造函数"<<endl;
}

Teacher::Teacher(string _name,string _id):name(_name),id(_id){

}

Teacher::Teacher(Teacher &teacher){
name=teacher.name;
id=teacher.id;
}
Teacher::~Teacher(){
cout<<"Teacher"<<name<<"的析构函数"<<endl;
}
void Teacher::teacherStudent(){
cout<<id<<"号"<<name<<"正在教导学生"<<endl;
}

- 构造函数

- 初始化一个类的对象,用于对类的各个属性进行赋值

给自己定义的私有变量赋初值的。在初始化对象时调用。自己写后,系统不会再生成什么事情都不干的默认构造函数了。

注意如果类的成员含有引用类型的数据成员,则该成员的初始化必须在构造函数的初始化列表中完成。(因为引用必须在初始化时完成赋值,以后不能改)

- 习惯上将类的各个属性在构造函数的初始化列表中进行赋值。如:Teacher::Teacher(string _name,string _id):name(_name),id(_id){}

- 拷贝构造函数

- 用类的一个对象初始化另一个对象,参数必须对当前类的引用

一般用默认的拷贝构造函数就行了,但是如果成员变量有指针时,涉及到深复制与浅复制,需要重写拷贝构造函数。防止一个类的两个对象的指针成员指向同一块内存。

浅复制:默认的拷贝构造函数时浅复制,即简单的分配好空间后,将原来的所有成员变量给新的对象的成员变量复制一份。但是如果成员变量有指针,则会使新旧对象的那个成员变量均指向一块相同的空间(这样一个变,另一个也变,和我们想要的不一样)(我们希望的复制应该是给新的对象分配空间,并将指针指向空间的内容拷贝过来)。

深复制:对于成员函数是指针时,给其分配空间,并将新的对象的指针成员指向该空间,然后给该空间赋值。

有三种调用情况:
Teacher b("xiaoMing","001"); Teacher a=b;或者 Teacher a(b)
函数参数为Teacher类对象
函数的返回值为Teacher类对象

- String的拷贝构造函数不需要delete []str.因为调用拷贝构造为初始化时,传参,或者返回时,这个时候因为前面没有用构造函数,就没有给str分配空间。故不用delete []str
- 注意Point a(1,2);Point b=a;与Point b(a)均调用拷贝构造函数。而Point b; b=a;调用重载的=函数

- 析构函数

- 在类的对象所占的空间被释放前调用,主要用于释放类中指针对应的动态空间

最好释放前加一个判断如 if(a==NULL)
例子:
构造函数中:str=new char[length+1];
析构函数中:
if(str!=NULL){
delete []str; //这样删除比较安全,先判断是否为NULL
}

- 其他函数

- 自己定义的当前类的常用方法

- 访问控制

- public 公开的,谁也能访问。 protected保护的,可以被派生类访问,但不能被外面的对象访问。 private私有的,只能由当前类的成员函数去访问,派生类和其他均不能访问。

- 使用: Clock c(1,2,3); Clock *c1; Clock &c2=c; c1=&c; 然后是具体使用: c.showTime() c1->showTime() c2.showTime();//c2是别名,还是对象,不是指针,所以用 '.'

### 类的组合·

- 作用:一个类可能由多个类组成,如汽车由发动机类,轮胎类,方向盘类等组成
- 构造函数

- 形式:类名::类名(形参表):内嵌对象1(对象1的类的构造函数的形参表),内嵌对象2(对象2的类构造函数的形参表)

class B{
  B(int x);
};
class C{
  C(int y);
};
Class A{
public:
  A(int x);
  A(A &a);
private:
  B b;
  C c;
};

A::A(int x):b(x),c(x){
  cout<<"A的构造函数的函数体"<<endl;
}
A::A(A &a):b(a.b),c(a.c){
  cout<<"A的拷贝构造函数的函数体"<<endl;
}

- A类的所有组合成员类必须写在A的构造函数的初始化列表中(抽象类除外),如果不写,则调用不写的成员的默认的构造函数,如无默认的(自己写了别的),则报错。 理解方法:即使不是自己定义的类的成员,是普通成员如int型,其也是在初始化列表初始化的,故所有成员均要在初始化列表中明确初始化,而不要让其在初始化列表初始化后再在构造函数体中更改其值
- 构造函数执行顺序:首先执行初始化列表上的成员的构造函数(与定义成员的顺序一致,而与初始化列表上成员的初始顺序无关),然后执行当前类的函数体。(构造函数的具体执行顺序后面会总结)

- 析构函数执行顺序和构造函数执行顺序正好相反
- 拷贝构造函数和构造函数相比只是参数不一样,其他初始化列表等都相似
- 前向引用声明

- 作用:A类内部有B类对象,B类内部有A类对象,此时因为A声明在前,不知道B是一个类,所以,需要前向引用声明
- 形式

class B; //因为A类中有B类对象所以,需要提前声明
class A{
public:
  void f(B *b);//必须写成指针或者引用的形式,不能写成值得形式,因为此时B类还没有定义
private:
  B &b;//不能写B的对象,只能写B的指针或者引用
};
class B{
 public:
  void f(A a);
private:
  A a;
};

### 类的友元

- 作用:使类的友元函数可以直接访问类的私有成员,使类的友元类的所有函数均可以直接访问类的私有成员
- 友元包括类的友元函数,类的友元类。意思是这个友元函数和友元类不是当前类的函数或者成员,但是可以直接访问当前类的私有成员。
- 实现:将函数声明或友元类写在类的内部,并加上一个friend

class A{
public:
  A();
  friend float add(A &a,A &b);//在此处加一个friend代表是A类的友元函数,该函数内部可以直接访问A类中的私有变量。实现时写: float add(A &a,A &b) 不用加A::因为add函数不是A的成员函数
  friend class B;//将B类声明为A类的友元类,B类中所有函数均可以直接访问A类中的私有变量。
};

- 友元关系不能传递,并且是单向的,并且不能被继承。
- 友元函数仅在声明时需要friend关键字,实现时不需要。显示错误:a friend function can only be declared in a class

### 类的继承与派生

- 作用:在原有类的基础上添加新的特殊属性。如基类为汽车,派生类为公共汽车和小轿车。两个派生类有相似的属性,也有其特有的属性。
- 派生类构成

- 构造函数

- 若基类有含参构造函数的非默认构造函数,则派生类的初始化列表中必须对其进行初始化
- 调用基类的构造函数,调用顺序为按照他们被继承时声明的顺序(从左到右)

- 拷贝构造函数

- 当成构造函数去写,只有参数不同,因为用的时一个对象的引用,所有赋值时,用对象的引用的成员变量去初始化。

- 析构函数

- 派生类的析构函数的执行顺序和构造函数相反。

- 类型兼容原则:需要基类对象的任何地方,都可以使用派生类的对象来替代,替代后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的部分。

类型兼容原则包括3中形式:派生类对象转换为基类对象 、派生类对象初始化基类引用 、 派生类指针转换为基类指针。

- 函数同名隐藏:如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏
- 虚基类

- 作用:解决一个类同时继承两个类A,B。而A和B同时继承与一个基类C时,所产生的二义性问题
- 详细说明:如果有类base1,然后base2和base3都继承于base1,然后又类base4继承于base2和base3.这时base4中会有两个base1的副本,访问base1的成员时容易产生二义性,并且浪费空间,可以虚继承,让base2和base3都虚继承base1,这样就会只留有一个base1的副本,不会产生二义性,且节省空间。
- 使用方法及构造函数

使用方法: class base2:virtual public base1    class base3:virtual public base1      class base4:public base2,public base3
构造函数: base4(int var):base1(var),base2(var),base3(var){ ......}

- 构造函数的执行顺序为:如果有虚基类,则首先执行虚基类的构造函数,然后执行基类的构造函数(虚基类部分不再执行)(以基类的派生顺序),派生类构造函数的初始化列表(以成员变量的声明顺序),派生类构造函数。
- 构造函数执行顺序: 虚基类---基类(不在执行虚基类部分的构造函数)-----派生类的新增成员的构造函数(对于类,出现在初始化表中,执行对应的构造函数,不出现在初始化列表中,执行默认的无参构造函数,如果没有无参构造函数则报错。对应int等基本类型,出现则初始化,否则什么也不做)-----派生类构造函数的函数体

### 多态性

- 重载多态

- 函数重载

- 作用:函数同名,但是参数列表不同,根据调用参数不同去调用不同的函数
- 实现:普通函数重载,函数同名,参数列表不同

/*
 * main.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include <iostream>
#include "Teacher.h"
using namespace std;

void show(int x){
cout<<"整数x="<<x<<endl;
}
void show(string x){
cout<<"字符串x="<<x<<endl;
}
int main()
{
show(1);
show("hello");
return 0;
}

- 类的成员函数重载:和普通函数重载功能和写法一样
- 只要派生类有基类同名函数,即使参数列表不同,基类同名的全部函数均被隐藏,只能用类名::函数名访问。原因:只有在相同作用域中定义的函数才可以重载,所以派生类中定义的同名函数会隐藏基类所有同名函数

- 运算符重载

- 用数学符号如:+,-或者cout<<等处理类,让其看起来更方便直观。

重载为类的成员函数时,表达式A op B就相当于调用A.operator op(B),若是A++,则相当于调用A.operator++(0),这里0只用来区分前置还是后置。若是重载为类的友元函数,A op B就相当于调用operator op(A,B), 若是A++,则相当于调用operator++(A,0)

- 实现

- 重载为类的非静态成员函数:对于+,此时第一个操作数为当前对象,第二个操作数为传入的对象
- 重载为非成员函数:对于+,此时传入两个操作数
- 具体实现:实际上和一般函数一样,就是函数名是固定的为: operator + operator <<等

/*
 * MyString.h
 *
 * Created on: 2020年7月3日
 *   Author: eastDong
 */

#ifndef MYSTRING_H_
#define MYSTRING_H_

#include <iostream>
using namespace std;

class MyString{
public:
MyString();
MyString(char *_mystr);
MyString(const MyString &_mystr);
~MyString();
MyString operator +(const MyString &strtwo);//重载为成员函数
MyString& operator=(const MyString &strtwo);//c++中临时变量不能作为非const的引用参数,所以为了防止用临时变量赋值时报错,加上const
friend ostream& operator <<(ostream &out,const MyString &_mystr);//重载为非成员函数

private:
char* mystr;
int length;
};



#endif /* MYSTRING_H_ */



/*
 * MyString.cpp
 *
 * Created on: 2020年7月3日
 *   Author: eastDong
 */
#include "MyString.h"
#include <string.h>

MyString::MyString(){
mystr=NULL;
length=0;
}

MyString::MyString(char*_mystr){
length=strlen(_mystr);
mystr=new char[length+1];
strcpy(mystr,_mystr);
}
MyString::MyString(const MyString &_mystr){
length=_mystr.length;
mystr=new char[length+1];
strcpy(mystr,_mystr.mystr);
}
MyString::~MyString(){
delete []mystr;
}

MyString MyString::operator +(const MyString &strtwo){
MyString mystr;
mystr.length=this->length+strtwo.length;
mystr.mystr=new char[mystr.length+1];
strcpy(mystr.mystr,this->mystr);
strcpy(mystr.mystr+this->length,strtwo.mystr);
return mystr;
}
MyString& MyString::operator=(const MyString &strtwo){
if(this==&strtwo){
return *this;
}
if(this->length!=0){
delete []mystr;
}
length=strtwo.length;
mystr=new char[length+1];
strcpy(mystr,strtwo.mystr);
return *this;
}

ostream& operator <<(ostream &out,const MyString &_mystr){
out<<_mystr.mystr<<"  ";
return out;
}



/*
 * main.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include <iostream>
#include "MyString.h"
using namespace std;

int main()
{
MyString mystr1("hello world!");
MyString mystr2("hi world!");
MyString mystr3(mystr1);
MyString mystr4=mystr1;
MyString mystr5,mystr6;
cout<<mystr1<<mystr2<<mystr3<<mystr4<<endl;
mystr5=mystr1;
mystr6=mystr1+mystr2;
cout<<mystr5<<mystr6<<endl;


return 0;
}

- 常用运算符重载时头文件写法

MyString& operator =(MyString &mystr); return *this;
MyString operator +(const MyString &mystr);
friend ostream& operator<< (ostream &out,MyString &mystr);

注意的是,传入的参数为引用是为了保证速度快(不用复制构造函数新建变量),但是最好加上const(因为如果传入的引用为临时变量会报错,加上const才不会出问题)

赋值运算符=返回为引用是因为:要对对象a赋值,并且重载为类的非静态成员函数,故第一个参数即为调用对象a,在函数内对当前对象修改后,返回*this即可。

- ++运算符的重载

- Clock& operator++() 为前置单目运算符

因为是前置单目运算符,所以先算结果,然后将当前的结果用于计算,故用Clock&

- Clock operator++(int) 为后置单目运算符

因为是后置单目运算符,所以要先对对象自增,然后将自增前的结果返回。自增前的结果因为是新创建的局部变量,所以不能用引用,只能用变量

- 赋值运算符=的重载

- 赋值运算符重载只能使用成员函数的方法来实现
- 重载= 号时,返回的是引用.一般有一个判断if(this==&other) return *this;
- 对于=号,在定义变量并初始化时,调用的是复制构造函数,对于已经定义好的变量,在赋值时,调用的是重载的=号函数。

- 注意输入输出操作符重载的写法,且必须声明为友元函数

friend ostream& operator<<(ostream& out,Complex& c); //流输入输出操作符

- vc6老编译器重载<<错误:'operator <<' is ambiguous

解决方法:
VC6.0 需要先声明类和重载操作符
class Time;
ostream& operator<<(ostream& o, const Time& t);
也就是:
#include <iostream>
using namespace std;
class CA;
ostream &operator<<(ostream &output,CA &ca); //此二行需放在类定义的前面(若有.h文件,就放在.h文件最前面,若无,就直接放在定义类以及此重载函数(.cpp文件)的前面)可详见电脑中家教程序Pointer Elevator程序

- C++中原来没有的运算符不能重载,三元运算符” ? : ”,类属关系运算符”.”,成员指针运算符”.*”,作用域运算符”::”都不能重载。

- 强制多态

- 强制将变量类型变化,以符合函数或者操作的要求。如将整数视为浮点数

- 包含多态

- 虚函数

- 作用:让基类指针或引用可以调用派生类的相应的函数。比如定义一个基类汽车,有函数为开车。然后派生类为公共汽车和小汽车,然后用派生类去初始化基类指针,此时可以用基类指针调用相应派生类的开车函数

注意只有通过基类的指针或引用调用虚函数时,才会发生动态绑定,而单纯用基类的对象调用只会调用基类的函数,因为:基类的指针可以指向派生类的对象,基类的引用可以指向派生类的别名,但基类的对象却不能表示派生类的对象。

- 基本实现:让基类相应函数声明时加上virtual,派生类声明相应函数时virtual加不加都行。实现虚函数时不能加virtual

/*
 * Human.h
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */

#ifndef HUMAN_H_
#define HUMAN_H_
#include <iostream>
using namespace std;

class Human{
public:
Human();
Human(string _name,string _sex,int _age);
virtual void work();//此函数为虚函数,如果Human的指针用其派生类初始化,则通过指针调用此函数时会调用其派生类对应的函数。
private:
int age;
string sex;
string name;
};
#endif /* HUMAN_H_ */
/*
 * Human.cpp
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */
#include "Human.h"

Human::Human(){
name="未命名";
sex="外星人";
age=0;
}

Human::Human(string _name,string _sex,int _age){
name=_name;
sex=_sex;
age=_age;
}
void Human::work(){
cout<<"这个是人工作的虚函数"<<endl;
}
/*
 * Driver.h
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */

#ifndef DRIVER_H_
#define DRIVER_H_
#include "Human.h"
class Driver:public Human{
public:
Driver();
Driver(string _name,string _sex,int _age,int _driverTime);
virtual void work();
private:
int driverTime;
};
#endif /* DRIVER_H_ */
/*
 * Driver.cpp
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */
#include "Driver.h"
#include "Human.h"
Driver::Driver():Human(){
driverTime=0;
}
Driver::Driver(string _name,string _sex,int _age,int _driverTime):Human(_name,_sex,_age),driverTime(_driverTime){

}

void Driver::work(){
cout<<"司机正在开车"<<endl;
}

/*
 * main.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include <iostream>
#include "Driver.h"
#include "Human.h"
using namespace std;

int main()
{
Human* a[2];
a[0]=new Human("张三","男",13);
a[1]=new Driver("李四","男",25,5);
a[0]->work();
a[1]->work();
return 0;
}

运行结果:
这个是人工作的虚函数
司机正在开车

- 纯虚函数

- 作用:既然虚函数是为了调用派生类的函数,基类函数实现不重要,索性基类函数就不写
- 实现:不写大括号及函数体,在后面加上=0

virtual void display()=0;//纯虚函数,此时只在.h文件中写即可,不需要写函数实现

抽象类:
在抽象的基类中: virtual void push(T x)=0;
派生类中:    void push(T x); (virtual带不带都行)(但是实现是必须不能带)

- 抽象类:含有纯虚函数的类就是抽象类,不能被实例化

- 作用:统一接口。可以在抽象类中定义好一个大的类别的行为,而不定义其属性。
- 基类是抽象类,不能被实例化,所以在派生类中不能调用初始化基类对象,也不能用new,因为new产生临时对象。初始化列表中不用写对基类的初始化。
- 不能定义抽象类的对象,,但是可以声明一个抽象类的指针或者引用,用过该指针或者引用,可以指向并访问派生类对象,具有多态性。

- 虚析构函数

- 作用:用派生类初始化的基类指针用完后,调用析构函数,如果是普通的析构函数,则只会调用基类的析构函数。而如果基类是虚析构函数,则会调用基类和派生类的析构函数。主要用于派生类的内部成员有指针,需要释放空间的情况
- 实现:在基类的析构函数加一个virtual

/*
 * Human.h
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */

#ifndef HUMAN_H_
#define HUMAN_H_
#include <iostream>
using namespace std;

class Human{
public:
Human();
Human(string _name,string _sex,int _age);
virtual ~Human();
virtual void work();//此函数为虚函数,如果Human的指针用其派生类初始化,则通过指针调用此函数时会调用其派生类对应的函数。
private:
int age;
string sex;
string name;
};
#endif /* HUMAN_H_ */
/*
 * Human.cpp
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */
#include "Human.h"

Human::Human(){
name="未命名";
sex="外星人";
age=0;
}

Human::Human(string _name,string _sex,int _age){
name=_name;
sex=_sex;
age=_age;
}
void Human::work(){
cout<<"这个是人工作的虚函数"<<endl;
}
Human::~Human(){
cout<<"Human的析构函数"<<endl;
}
/*
 * Driver.h
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */

#ifndef DRIVER_H_
#define DRIVER_H_
#include "Human.h"
class Driver:public Human{
public:
Driver();
Driver(string _name,string _sex,int _age,int _driverTime,int _carNumber,string *_carName);
virtual ~Driver();
virtual void work();
private:
int driverTime;
int carNumber;
string *carName;
};
#endif /* DRIVER_H_ */
/*
 * Driver.cpp
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */
#include "Driver.h"
#include "Human.h"
Driver::Driver():Human(){
driverTime=0;
carNumber=0;
carName=NULL;
}
Driver::Driver(string _name,string _sex,int _age,int _driverTime,int _carNumber,string *_carName):Human(_name,_sex,_age),driverTime(_driverTime),carNumber(_carNumber){
carName=new string[carNumber];
for(int i=0;i<carNumber;i++){
carName[i]=_carName[i];
}
}

void Driver::work(){
cout<<"司机正在开车"<<endl;
}

Driver::~Driver(){
delete []carName;
cout<<"Driver的析构函数"<<endl;
}
/*
 * main.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include <iostream>
#include "Driver.h"
#include "Human.h"
using namespace std;

int main()
{
Human* a[2];
string name[2]={"奔驰","宝马"};
a[0]=new Human("张三","男",13);
a[1]=new Driver("李四","男",25,5,2,name);
a[0]->work();
a[1]->work();
delete a[0];
delete a[1];
return 0;
}

运行结果:
这个是人工作的虚函数
司机正在开车
Human的析构函数
Driver的析构函数
Human的析构函数

- 派生类中的虚函数和纯虚函数还会隐藏基类中同名函数的所有其他重载形式。即通过派生类对象不能访问基类同名非虚重载函数
- 如果是在构造函数或析构函数中调用虚函数,则采用静态联编,调用当前类中的虚函数。而其他普通成员函数调用虚函数采用动态联编,调用派生类最后实现的虚函数。

即在基类的构造和析构函数中调用虚函数,则调用当前基类中的虚函数。
而在其他函数中调用虚函数,则调用派生类中实现的虚函数
(比如说一个基类指针指向派生类。派生类创建过程中首先执行基类的构造函数。如果基类的构造函数调用虚函数,则执行基类中写的虚函数。然后假如基类构造函数还执行其他函数,而其他函数内部执行虚函数,此时会动态联编,执行派生类中实现的虚函数。)

- 当缺省参数和虚函数一起出现的时候情况有点复杂,极易出错。我们知道,虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的。

class B{ virtual void vfun(int i = 10);}
class D : public B{virtual void vfun(int i = 20);}
D* pD = new D();      B* pB = pD;
pD->vfun();    pB->vfun();
缺省参数是静态绑定的,pD->vfun()时,pD的静态类型是D*,所以它的缺省参数应该是20;同理,pB->vfun()的缺省参数应该是10。

- 参数多态

- 模板

- 函数模板

- 作用:给一些参数不确定的函数用一个T去替代。如add方法在写之前不知道是int还是float,可以用T替代,而不用单独写一个int的add和float的add
- 实现:在函数前面加上 template<class T>

/*
 * Template.h
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */

#ifndef TEMPLATE_H_
#define TEMPLATE_H_

template <class T>
T add(T x,T y){
return x+y;
}

template <class T>
T sub(T x,T y){
return x-y;
}

#endif /* TEMPLATE_H_ */
/*
 * main.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include <iostream>
#include "Template.h"
using namespace std;

int main()
{
int a=3,b=4,c;
c=add(3,4);
cout<<"c="<<c<<endl;
c=sub(3,4);
cout<<"c="<<c<<endl;
return 0;
}

- 注意将其函数模板放入.h文件中

- 类模板

- 作用:如栈类,因为事先不知道存什么类型的元素,所以用模板进行创建类,只创建一次即可,不用单独为int和float创建不同的栈类
- 实现:定义时加上template <class T>如:template <class T> class Stack。对此类进行派生或者写其函数时,将其实例化即template <class T> 然后Stack<T>。可以理解为不实例化不能用,实例化要用Stack<T>但是不知道T是什么,所以加上template <class T>

/*
 * Stack.h
 *
 * Created on: 2020年7月4日
 *   Author: eastDong
 */

#ifndef STACK_H_
#define STACK_H_
#include <iostream>

using namespace std;



template <class T>
class Stack
{
protected:
int length;
public:
virtual void push(T x)=0;
virtual T pop()=0;
virtual T top()=0;
};



template <class T>
class StackOne:public Stack<T>
{
private:
int num;
T* base;
public:
StackOne();
~StackOne();
void push(T x);
T pop();
T top();
};
template<class T>
StackOne<T>::StackOne(){
this->length=0;
num=3;
base=new T[num];
}
template <class T>
StackOne<T>::~StackOne(){
delete []base;
}
template <class T>
void StackOne<T>::push(T x){
int i;
if(this->length==num){
T* newbase=new T[2*num];
for(i=0;i<num;i++){
newbase[i]=base[i];
}
delete []base;
base=newbase;
num=2*num;
}
base[this->length]=x;
this->length++;
}

template<class T>
T StackOne<T>::pop(){
if(this->length==0){
return NULL;
}else{
(this->length)--;
return base[this->length];
}
}

template<class T>
T StackOne<T>::top(){
if(this->length==0){
return NULL;
}else{
return base[this->length-1];
}
}

template<class T>
class node
{
public:
T value;
node* next;
node(T x=NULL,node *n=NULL){
value=x;
next=n;
}
};

template <class T>
class StackTwo:public Stack<T>
{
private:
node<T> *currtop;
public:
StackTwo();
~StackTwo();
void push(T x);
T pop();
T top();
};

template<class T>
StackTwo<T>::StackTwo(){
this->length=0;
currtop=new node<T>(NULL,NULL);
}

template<class T>
StackTwo<T>::~StackTwo(){
node<T> *p;
while(currtop->next!=NULL){
p=currtop;
currtop=currtop->next;
delete p;
}
delete currtop;
}

template<class T>
void StackTwo<T>::push(T x){
(this->length)++;
node<T> *p=new node<T>(x,currtop);
currtop=p;
}

template<class T>
T StackTwo<T>::pop(){
if(this->length==0){
return NULL;
}else{
node<T> *p=currtop;
T t=currtop->value;
currtop=currtop->next;
delete p;
this->length--;
return t;
}
}

template<class T>
T StackTwo<T>::top(){
if(this->length==0){
return NULL;
}else{
return currtop->value;
}
}


#endif /* STACK_H_ */


/*
 * main.cpp
 *
 * Created on: 2020年6月30日
 *   Author: eastDong
 */
#include <iostream>
#include "Stack.h"
using namespace std;


int main()
{
Stack<int> *s=new StackTwo<int>();
s->push(1);
s->push(2);
s->push(3);
s->push(4);
s->push(5);
s->push(6);
for(int i=0;i<6;i++){
cout<<s->pop()<<" ";
}
cout<<endl;
return 0;
}

- 以抽象的类模板为基类派生新类

基类模板:
template <class T>
class Stack

派生类:
template <class T> //带这个说明是类模板,然后才能用T
class StackOne:public Stack<T> //继承的是一个类对象,所以用Stack<T>
要注意基类中不用private,因为public继承后,基类不能访问,要用protected。
//基类的private成员被继承(无论哪种继承方式)后,永远是不可访问状态。 
或者一个笨方法,将派生类设成基类的友元类(但要注意因为派生类定义在基类后面,所以在基类声明前,先要声明派生类)
如:

template <class T>
class StackOne;

template <class T>
class Stack
{
public:
friend class StackOne<T>;


写模板类的成员函数时记得:
template<class T> 
T Stack<T>::top()

- 定义模板基类A,再定义模板派生类B继承于A,此时使用A中变量或方法时,必须通过this->形式,否则编译器报错。若将类A、B修改为非模板类,则可直接使用基类A中的数据a,无需this->。
- node<T> *p=new node<T>(x,currtop);//因为你定义的节点类是类模板,所以必须带上<T>,实例化为对象才能用
- 将类模板和其实现均放入同一个.h文件中

### 注意事项

- 指针对象没有给它分配空间,不会调用构造函数。如:AB a(2),b[3],*p[4],只调用构造函数四次
- struct 在c++中和类唯一的区别是默认的访问控制属性不同。class中是private,struct中是public.有struct只是为了保持c++对c的兼容
- union 多个成员共享最长的那个成员的空间和c一样
- 成员函数可以带默认形参值,但要最好将默认形参值写在函数声明处(即类内声明部分,而不是类外成员函数实现部分。
- 对于类的成员函数,只要实现写在类内,就相当于声明为内联了。写在类外,如果想是内联,则需要加inline,(唯一个特殊,推荐inline写在实现部分,而不是声明)
- 多个类的文件格式

//head.h
#ifndef HEAD_H
#define HEAD_H

class Point{

};
#endif
//这样写,当你写#include "Point.h"时,即使多次写,也只包含一次,不会重复定义

- 成员函数也可以重载,但要注意,派生类的一个同名函数会隐藏所有基类中所有的同名函数。
- 注意静态数据成员的声明和定义:static int x; int Test::x=30

对静态数据成员初始化只能在类外进行,一般在在类声明与 main()之间的位置。
格式为:数据类型 类名∷静态数据成员名=值;

- p是一个指向类 A数据成员 m的指针, A1是类A的一个对象。如果要给 m赋值为5。则为:A1.*p=5
- 设类A有成员函数 void f(void) ;若要定义一个指向类成员函数的指针变量 pf 来指向f,该指针变量的声明语句是:void (A::*pf)(void)=&A::f
- 常数据成员只能在构造函数中初始化,在其他地方他的值不能被修改,而且其初始化只能通过构造函数的初始化列表进行
- 定义类的指针,若未给它分配空间,不会调用构造函数
- 封装是编译期的概念,是针对类型而非对象的,在类的成员函数中可以访问同类型实例对象的私有成员变量.

总结:C++的访问修饰符的作用是以类为单位,而不是以对象为单位。通俗的讲,同类的对象间可以“互相访问”对方的数据成员,只不过访问途径不是直接访问.步骤是:通过一个对象调用其public成员函数,此成员函数可以访问到自己的或者同类其他对象的public/private/protected数据成员和成员函数(类的所有对象共用),而且还需要指明是哪个对象的数据成员(调用函数的对象自己的成员不用指明,因为有this指针;其他对象的数据成员可以通过引用或指针间接指明)

## STL

### 容器

- 作用:顾名思义,装你要存放的元素
- 常用功能:s1 op s2 begin end clear size empty s1.swap(s2) 。注意 s.end()//指向末尾,即最后一个元素的下一个位置
- 分类

- 顺序容器

- 分类

- 向量vector

- vector为一个动态分配的数组,有容量capacity和现有大小size的区别。适合往后插入和删除 。
- 特有方法: capacity s.reserve(n)
- 清除不用的容量: vector<int>(s.begin(),s.end()).swap(s)

- 双端队列deque

- deque是一个将元素分段存放在一个个大小固定的数组中,支持随机访问,支持像两端高效的插入和删除元素的容器。维护了一个索引数组。
- 两端插入时,迭代器失效(和索引数组有关),但是指针和引用不会失效

- 列表list

- list是双向链表,有一个特殊操作 接合:splice 将s2的部分接到s1中,并删除s2中那一部分。

- 通过3种顺序迭代器实现了栈,队列,和优先级队列3中适配器

- 栈 stack 方法有 s.pop() push top size empty
- 队列 queue 方法有s.pop() push size empty front back front获得的是队头元素的引用
- 优先级队列priority_queue 方法有s.pop() push top size empty 访问和栈一样都是top,pop出来的是队列中最大的一个,但是不支持栈和队列的比较op等操作

- 常用方法:assign insert erase resize front back push_back pop_back
- list和deque有vector没有的是: push_front pop_front

- 关联容器

- 分类

- 集合set:

- 插入: pair<set<double>::iterator,bool> r=s.insert(k);

- 多重集合multiset
- 映射map

- map<int,double>的元素类型是pair<int,double>
- 插入

- courses.insert(make_pair("CSAPP",3)); 需要头文件<utility>
- courses.insert(pair<string,int>("CSAPP",3));

- 查找

- map<string,int>::iterator iter=courses.find(k);//不用写成pair<>的形式
- 重载了[],如果查找键值为3可以写成s[3],更改时s[3]="23",如果3不存在,则s[3]会插入一个键值为3的pair,其第二个值为(v是附加类型)v().加入第二个值为整数,则为0

- 多重映射multimap

- pair<multimap<string,string>::iterator,multimap<string,string>::iterator>range=couse.equal_range(name)//查找等于的

- 常用方法

- insert 返回类型为 pair<S::iterator,bool>
- find找不到时返回s.end()
- lower_bound第一个大于等于的迭代器
- upper_bound第一个大于的迭代器
- equal_range返回一个pair<S::iterator,S::iterator>的区间
- erase count 等方法

- 关联容器的插入和删除操作不会使任何已有的迭代器,指针或引用失效
- 其内部是顺序排列的

### 迭代器

- 作用:iterator是泛化的指针,其头文件<iterator>
- 分类

- 输入迭代器,输出迭代器:适用于只需遍历序列一次的算法的输入。InputIterator OutputIterator
- 前向迭代器:即可读,又可输,还可对序列进行可重复的单向遍历。
- 双向迭代器:单向的基础上支持反向移动。
- 随机迭代器:双向的基础上,支持一下子移动n位。如vector的begin和end得到的迭代器,指针也是随机访问迭代器。 RandomAccessIterator

- 尽量用++iter 而不要用iter++。 后置的++容易出问题
- 迭代器类型永远是 S::iterator如 set<double>::iterator it=s.find(k); map<string,int>::iterator iter=courses.find(k);//不用写成pair<>的形式
- 指针p是随机访问迭代器
- 输入和输出迭代器

- istream_iterator<double>(cin); 输入流迭代器,指向输入流的开始位置。 *cin cin++ cin->均可用
- istream_iterator<double>();输入流迭代器,指向输入流的结束位置
- ostream_iterator<int>(cout,",") 输出迭代器,指向输出流cout,可选让分隔符为“,”

- 辅助函数

- void advance(InputIterator iter,Distance n)//让iter前进n个元素,即可以向随机访问迭代器一样使用,双向时n可以取负值
- unsigned distance(InputIterator first,InputIterator last)//first经过多少次运算后到达last

- 个别使用范例

- copy(istream_iterator<int>(cin),istream_iterator<int>(),back_inserter(s)); //即将标准输入得到的数据插入容器s的末尾.还有front_inserter back_inserter inserter等方法
- copy(s1.rbegin(),s1.rend(),ostream_iterator<int>(cout," ")//逆序输出
- 逆向迭代器 rbegin rend 其++被映射为--故可用++ 如 list<int> l(s.rbegin(),s.rend());

### 函数对象

- 作用:给通用算法提供不同的功能。如给排序算法确定是从大到小排还是从小到大排
- 含义:函数对象是一个行为类似函数的对象,其功能是获取一个值,或者改变操作的状态。可以是任何函数和重载了()运算符的类的对象,其头文件是<functional>

例如: int mult(int x,int y){return x*y;};
 class MultClass{public: int operator()(int x,int y){return x*y;}};
 调用: accumulate(a,a+N,1,mult);//accumulate要求二元函数
     accumulate(a,a+N,1,MultClass());

- 分类

- 返回与传入相同的,根据传入参数为0,1,2个分为:产生器,一元函数,二元函数

如:plus minus multiplies divides modulus negate取反  
用法: accumulate(a,a+N,1,multiplies<int>())

- 返回值为bool型的,根据传入参数为1,2个分为:一元谓词,二元谓词

如:equal_to not_equal_to 需要重载==
greater less greater_equal less_equal 需要重载<
logical_and logical_not logical_or
用法: sort(a.begin(),a.end(),greater<int>());//从大到小排序
negate<int>() //返回整数的相反数

- 函数适配器

- 作用:改造已有的函数,使其适合算法。如有一个判断大小的函数,给第二个参数绑定一个固定值,成了判断是否大于固定值的函数
- bind1st bind2nd 返回一个绑定了左值或者右值得一元函数(原来是二元的)

vector<int>::iterator p=find_if(a.begin(),a.end(),bind2nd(greater<int>(),40);

- not1 not2 将原来返回的bool值取反,分别对应一元谓词和二元谓词
- mem_fun mem_fun_ref 使用类的成员函数时用的,一个对应指针参数类型,一个对应引用参数类型

如: 类Car有display() 函数 vector<Car *>pcars  vector<Car> cars;
for_each(pcars.begin(),pcars.end(),mem_fun(&car::display));
for_each(cars.begin(),cars.end(),mem_fun_ref(&car::display));

- ptr_fun 指向一元函数或者二元函数

如: bool g(int x,int y){return x>y;};
p=find_if(a.begin(),a.end(),bind2nd(ptr_fun(g),40));
p=find_if(a.begin(),a.end(),not1(bind2nd(greater<int>(),15));
p=find_if(a.begin(),a.end(),bind2nd(not2(greater<int>()),15));

### 算法

- 作用:提供一些普适的算法。其头文件<algorithm>
- 算法要求迭代器区间如: int a[5]; 则 a a+5可以是因为a是指针,指针是随机访问迭代器,相当于派生的最靠后一个类--随机迭代器(不同类型的迭代器间是父类与子类关系)。故可以当成输入流迭代器或者输出流迭代器等。istream_iterator<int>(cin) istream_iterator<int>() 也可以是
- 常用方法

- transform(s.begin(),s.end(),ostream_iterator<int>(cout," "),negate<int>());//遍历[s.begin,s.end)间所有元素,作为函数negate的参数,并将函数negate的返回值通过ostream_iterator顺序输出,然后返回最后一个元素的下一个位置即s.end()。

原型为:OutputIterator transform(InputIterator first,InputIterator last,OutputIterator result,UnaryFunction op);

- sort(s.begin(),s.end());//将[begin,end)区间内数据从小到大排列,排序结果放在原位。只能接受随机访问迭代器作为参数。

原型为 void sort(RandomAccessIterator first,RandomAccessIterator last);
sort(a.begin(),a.end(),greater<int>());//从大到小排序

- copy(s.begin(),s.end(),result);//将[begin,end)区间内部的数据通过输出迭代器result顺序输出。

原型为:OutputIterator copy(InputIterator first,InputIterator last,OutputIterator result);

- Type accumulate(InputIterator first,InputIterator last,Type val,BinaryFunction binaryOp);//要求二元函数对象,其初值为val,然后对迭代器区间的元素进行累积运算(根据二元函数),binaryOp为累加函数
- find_if(InputIterator first,InputIterator last,UnaryPredicate pred);查找在[first,last)范围内第一个pred(x)为真的元素如:vector<int>::iterator p=find_if(a.begin(),a.end(),bind2nd(greater<int>(),40);
- for_each(InputIterator first,InputIterator last,UnaryFunction func)//对[first,last)的每一个元素都调用func函数

*XMind: ZEN - Trial Version*

原文地址:https://www.cnblogs.com/eastDong/p/13279139.html