2021年C++教程最新版

一、C++教程前言介绍

这套C++教程能够很好的帮助你入门,让你掌握C++基础并且打开通向高级C++工程师的大门,通俗易懂深入浅出是这套教程最大的特点,让你能够很轻松地学习C++。

可以关注微 信 公 众 号 “C和C加加” 回复“ZXC”领取详细教程和更多进阶视频教程大全

二、基本语法

  • 对象-对象具有状态的行为。对象是类的实例。
  • 类-类可以定义为对象行为、状态的模版。
  • 方法-从基本上讲,一个方法表示一种行为,一个类可以包含多种方法。
  • 变量

1、注释

2、关键字

3、标识符

标识符是用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。

标识符内不允许出现标点字符,比如 @、& 和 %。C++ 是区分大小写的编程语言。

三、数据类型

1、第一个C++程序

 2、基本数据类型

七种基本的C++数据类型:bool、char、int、float、double、void、wchar_t

类型修饰符:signed、unsigned、short、long

注:一些基本类型可以使用一个或多个类型修饰符进行修饰,比如:signed short int简写为short、signed long int 简写为long。

3、数据类型在不同系统中所占空间大小

 这个与机器、操作系统、编译器有关。比如同样是在32bits的操作系统系,VC++的编译器下int类型为占4个字节;而tuborC下则是2个字节。

原因:

c/c++规定int字长和机器字长相同

操作系统字长和机器字长未必一致

编译器根据操作系统字长来定义int字长

4、typedef声明

5、枚举类型

C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合;枚举元素是一个整型,枚举型可以隐式的转换为int型,int型不能隐式的转换为枚举型。

如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始;

默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。

例如:

6 、常量及符号

(1)整型常量

整型常量可以分为有符号整型常量和无符号整型常量

八进制:以0开头,即以0作为八进制数的前缀,每位取值范围是0~7,八进制数通常是无符号数。

例如:016、0101、0128都是合法的八进制数
十六进制:以0x或者0X开头,其数码取值范围0 ~ 9,以及A ~ F或者a ~ f

例如:0X2A1、0XC5、0XFFFF都是合法的16进制数

(2)实型常量

小数表示法:由整数部分和小数部分组成,整数部分和小数部分每位取值范围是0~9,例如:0.0、3.25、0.00596、2.0
指数表示法:指数部分以符号"e"或者"E"开始,但必须是整数,并且符号"e"或"E"两边都必须有一个数,例如:1.2e20和-3.4e-2

(3)字符常量

字符常量是单引号括起来的字符,例如:'a'和'?'都是合法字符常量。字符'a'的ASCII码值是97,字符'A'的ASCII码值是41,字符'?'的ASCII码值是63

转义字符是特殊的字符常量,使用时以字符串’'代表开始转义,和后面不同的字符表示转义的字符。转义字符表如下所示:

 (4)字符串常量

是由一对双引号括起来的零个或多个字符序列,例如:“welcome to our school”、“hello world”。""可以表示一个空字符串。

字符常量’A’和字符串常量"A"是不同的,字符串常量"A"是由'A'和'\0'两个字符组成的,字符串长度是2,字符串常量'A'只是一个字符,没有长度。

(5)其他常量

  • 布尔常量:布尔常量只有两个,一个是true,表示真;另一个是false,表示假。
  • 枚举常量:枚举型数据中定义的成员也是常量,这将在后文介绍。
  • 宏定义常量:通过#define宏定义的一些值也是常量。例如:define PI3.1415。其中PI就是常量。

7、变量

变量其实只不过是程序可操作的存储区的名称。C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。

(1)标识符:是用来对C++程序中的常量、变量、语句标号以及用户自定义函数的名称进行标识的符号。
标识符命名规则:

  • 有字母、数字及下划线组成,且不能以数字开头。
  • 大写和小写字母代表不同意义。
  • 不能与关键字同名
  • 尽量“见名知义”,应该受一定规则的约束。
  • 不合法的标识符:6A、ABC*、case(保留字)

c++保留关键字,如图所示:

(2)变量与变量说明:变量使用前一定要定义或说明,变量声明的一般形式如下:[修饰符] 类型 变量名标识符

类型是变量类型的说明符,说明变量的数据类型。修改师傅是任选的,可以没有。
在这里插入图片描述

 (3)整型变量:整型变量可以分为短整型、整型和长整型,变量类型说明符分别是short、int、long。根据是否有符号还可分为以下6种。

  • 整型 [signed] int
  • 无符号整型 unsigned [int]
  • 有符号短整型 [signed] short [int]
  • 无符号短整型 unsigned short [int]
  • 有符号长整型 [signed] long [int]
  • 无符号长整型 unsigned long [int]

 (4)实型变量:又称为浮点型变量,变量可分为单精度(float)、双精度(double)和长双精度(long double)三种。

(5)变量赋值:变量值是动态改变的,每次改变都需要进行赋值运算。变量赋值的形式如下:变量名标识符 = 表达式,例如:

 (6)变量赋初值:可以在声明变量的时候就把数据赋给变量,这个过程叫变量赋初值,赋初值的情况有以下几种:

  • int x=5;:表示定义x为有符号的基本整型变量,赋初值为5
  • int x,y,z=6;:表示定义x、y、z为有符号的基本整型变量,z赋初值为6
  • int x=3,y=3,z=3;:表示定义x、y、z为有符号的基本整型变量,且赋予的初值均为3

(7)字符变量

  • 一个字符类型,即可以字符形式输出,也可以整数类型输出:

  • 允许对字符数据进行算术运算,此时就是对它们的ASCII码值进行算术运算: 

(8)变量的作用域

局部变量:在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。
全局变量:在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。

局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。
当局部变量被定义时,系统不会对其初始化;定义全局变量时,系统会自动初始化值:int float double 0,char ’\0‘,指针 NULL

8、自定义数据类型

(1)结构体

结构体可以包含不同数据类型的结构。

定义结构体的一般形式

结构体变量名的定义和初始化:

//定义结构体同时声明结构体变量名
struct 结构体类型名
{
	成员类型1 成员名1;
	成员类型2 成员名2;
	... ...
	成员类型n 成员名n;
}变量名1,变量名2,...变量名n;
//先定义结构体
[struct] 结构体类型名 变量名;
//直接定义
struct 
{
	成员类型1 成员名1;
	成员类型2 成员名2;
	... ...
	成员类型n 成员名n;
}变量名1,变量名2,...变量名n;
struct  person
{
	int year;
	int age;
	string name;
}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };
struct  person
{
	int year;
	int age;
	string name;
};
struct person p1 = { 2019,24,"heiren" }, p1 = { 2020,24,"heiren" };
struct 
{
	int year;
	int age;
	string name;
}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };

结构体变量的使用:

  • 具有相同类型的结构体变量可以进行赋值运算,但是不能输入输出
  • 对结构体变量的成员引用:结构体变量名.成员名
  • 指向结构体的指针变量引用格式:指针变量名->成员名;
    结构体数组的定义,初始化和使用与结构体变量、基本类型数组相似
struct person
{
	int year;
	int age;
	string name;
}p[2] ={ {2019,24,"heiren"}, { 2020,24,"heiren" }};//可以不指定数组元素个数
p[1].age;

结构体作为函数传递有三种:值传递,引用传递,指针传递

结构体大小和字节对齐
    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐.
    为什么需要字节对齐?各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。
三个个概念:

自身对齐值:数据类型本身的对齐值,结构体或类的的自身对齐值是其成员中最大的那个值,例如char类型的自身对齐值是1,short类型是2;
指定对齐值:编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;
有效对齐值:自身对齐值和指定对齐值中较小的那个。
字节对齐的三个准则
结构体变量的首地址能够被其有效对齐值的大小所整除
结构体的总大小为结构体有效对齐值的整数倍。
结构体每个成员相对于结构体首地址的偏移量都是有效对齐值的整数倍。
可以通过#pragma pack(n)来设定变量以n字节对齐方式

 (2)公用体(union)

几个不同的变量共享同一个地址开始的内存空间。

成员类型可以是基本数据类型,也可以是构造数据类型。
公用体变量初始化时,只能对第一个成员赋值。
公用体变量所占的内存长度等于最长的成员长度。
公用体变量在一个时刻只能一个成员发挥作用,赋值时,成员之间会互相覆盖,最后一次被赋值的成员起作用。
定义:

union 共同体类型名
{
    成员类型1 成员名1;
	成员类型2 成员名2;
	... ...
	成员类型n 成员名n;
};

初始化

union data
{
	int i;
	float f;
	char c;
}x = {123};
union data
{
	float f;
	int i;
	char c;
};
data x = {12.3};
union 
{
	char c;
	int i;
	float f;
}x = {’y‘};

引用

共同体变量名.成员名;
union data
{
	int i;
	float f;
	char c;
}x = {12};
int main()
{
	cout << x.i << " " << x.f << " " << x.c << endl;//12 1.68156e-44 
	x.c = 'c';
	cout << x.i <<" "<< x.f << " " << x.c << endl;//99 1.38729e-43 c
	return 0;
}

9、数据输入与输出

(1)控制台屏幕

(2)操作控制

在头文件iomanip.h中定义了一些控制流输出格式的函数,默认情况下整型数按十进制形式输出,也可以通过hex将其设置为十六进制输出。流操作的控制具体函数如下

 

 四、运算符与表达式

1、运算符

(1)算术运算符:算术运算主要指常用的加(+)、减(-)、乘(*)、除(/)四则运算,算术运算符中有单目运算符和双目运算符。

 

 

(2)关系运算符

关系运算符主要是对两个对象进行比较,运算结果是逻辑常量真或假。 

(3)逻辑运算符

逻辑运算符是对真和假这两种逻辑值进行运算,运算后的结果仍是一个逻辑值

 

(4)赋值运算符

(5)位运算符
在这里插入图片描述

 

 (6)移位运算符

 

 

 (7)sizeof运算符

(8)条件运算符 

 

(9)逗号运算符 

 (10)运算符优先级

(11) 运算符重载

所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载(Operator Overloading)也是一个道理,同一个运算符可以有不同的功能。

运算符重载是通过函数实现的,它本质上是函数重载。
允许重载的运算符

不允许重载的运算符

重载运算符遵循的规则:

  • 不可以自己定义新的运算符,只能对已有的C++运算符重载。
  • 不能改变运算符运算对象的个数。
  • 不能改变运算符的优先级和结合性
  • 应与标准类型运算功能相似,避免影响可读性。

一般格式:

函数类型 operator运算符(参数列表)
{
	函数体
}
//举个栗子:定义一个向量类,通过运算符重载,可以用+进行运算。
class Vector3
{
public:
	Vector3();
	Vector3(double x,double y,double z);
public:
	Vector3 operator+(const Vector3 &A)const;
	void display()const;
private:
	double m_x;
	double m_y;
	double m_z;
};
Vector3::Vector3() :m_x(0.0), m_y(0.0), m_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m_x(x), m_y(y), m_z(z) {}
//运算符重载
Vector3 Vector3::operator+(const Vector3 &A) const
{
	Vector3 B;
	B.m_x = this->m_x + A.m_x;
	B.m_y = this->m_y + A.m_y;
	B.m_z = this->m_z + A.m_z;
	return B;
}
void  Vector3::display()const
{
	cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}

 运算符重载的形式有两种:重载函数作为类的成员,重载函数作为类的友元函数
根据运算符操作数的不同:双目运算符作为类成员函数,单目运算符作为类的成员函数,双目运算符作为类的友员函数,单目运算符作为类的友元函数。

双目运算符作为友元函数时需要制定两个参数。
运算符重载函数作为类成员函数可以显式调用。

常用运算符的重载

  • 自增自减
  • 赋值运算符
  • 输入\输出运算符重载

2、表达式

(1)算数表达式

(2)关系表达式

(3)条件表达式 

(4)赋值表达式

(5)逻辑表达式 

(6) 逗号表达式

五、语法结构

1、while循环

 可以关注微信公众号 “C和C加加” 回复“ZXC”有更多详细教程领取!

while(conditon)//0为false,非0为true
{
	statement(s);
}

2、do…while循环

 3、while与do…while比较

 

 4、for

for(init;conditon;increment)//0为false,非0或什么也不写为true
{
	statement(s);
}
  • init首先被执,且只会执行一次,也可以不写任何语句。
  • 然后会判断conditon,true执行循环主体,false跳过循环
  • 执行完循环主体,执行increment,跳到2
int array[5] = { 11, 22, 33, 44, 55 };
for (int x : array)
{
	cout << x << " ";
}
cout << endl;
// auto 类型也是 C++11 新标准中的,用来自动获取变量的类型
for (auto x : array)
{
	cout << x << " ";
}
  • for each
    STL中的for增强循环。
int a[4] = { 4,3,2,1 };
for each (int var in a)
{
	cout << var << " ";
}

5、判断结构

  • if
if(expr)
{
	statement;//如果expr为true将执行的语句块 
}
if(expr)
{
   statement1;// 如果expr为true将执行的语句块
}
else
{
   statement2;// 如果expr为false将执行的语句
}
if(expr1)
{
   statement1;// 如果expr1为true将执行的语句块
}
elseif(expr2)
{
   statement2;// 如果expr2为true将执行的语句块
}
...
else
{
	statementElse;// 当上面的表达式都为false执行的语句块
}
  • switch
switch(expression){
    case constant-expression  :
       statement(s);
       break; 
    case constant-expression  :
       statement(s);
       break; 
    // 您可以有任意数量的 case 语句
    default : // 可选的
       statement(s);
}

每个case后满的常量表达式必须各不相同。
case语句和default语句出现的顺序对执行结果没有影响。
若case后没有break,执行完就不会判断,继续执行下一个case语句。直到遇到brerak。
default后面如果没有case,则break可以省略
多个case可以用一组执行语句

char c = 'A';
	switch (c)
	{
	
	case 'A':
	case 'B':
	case 'C':
		cout << "及格了" << endl;
		break;
	default:
		cout << "不及格" << endl;
	
	}

6、循环控制

 

 

7、循环嵌套 

 

 8、三元运算符

//如果 Exp1 为真,则计算 Exp2 的值,结果即为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ? 表达式的值
Exp1 ? Exp2 : Exp3;

9、预处理命令

预处理程序(删除程序注释,执行预处理命令等)–>编译器编译源程序

  • 宏定义:#define 标识符 字符串
  • 文件包含:#include<filename> 或者#include“filename”
  • 条件编译
//如果标识符被#define定义过,执行程序段1,否则执行程序段2
#ifdef 标识符
	程序段1
#else
	程序段2
#endif
//如果标识符没有被#define定义过,执行程序段1,否则执行程序段2
#ifndef 标识符
	程序段1
#else
	程序段2
#endif
//如果表达式为true,执行程序段1,否则执行程序段2
#if 表达式
	程序段1
#else
	程序段2
#endif

六、数组和指针

1、一维数组

 初始化的形式:数据类型 数组名[常量表达式] = {初值表};
为数组的某一个元素赋值:数组名[下标] =值(下标从0开始)
数组的引用:数组名[下标]

  • 初始化数组时,可以只给部分数组元素赋值
  • 对全部元素数组赋值时,可以不指定数组长度,编译系统会根据初值个数确定数组的长度。
  • static型数组元素不赋初值,系统会自动默认为0。

2、二维数组

定义一维数组的形式:数据类型 数据名[常量表达式1][常量表达式2]
初始化的形式:数据类型 数组名[常量表达式1] [常量表达式2]= {初值表};
为数组的某一个元素赋值:数组名[行下标][列下标] =值(下标从0开始)
数组的引用:数组名[行下标][列下标]

将所有数据写在一个花括号内,自动按照数组元素个数在内存中排列的顺序赋值
可对部分元素赋值,其余元素的值自动取0.
定义初始化数组时,可以省略第一维的长度,第二维不能省,系统会自动确认行数

3、字符数组

char类型的数组,在字符数组中最后一位为’\0’)时,可以看成时字符串。在C++中定义了string类,在Visual C++中定义了Cstring类。
字符串中每一个字符占用一个字节,再加上最后一个空字符。

4、指针

指针是一个变量,其值为另一个变量的地址。即内存位置的直接地址。
声明的一般形式:

  • 数据类型是指针变量所指向的变量的数据类型,*表示其后的变量为指针变量
数据类型 *指针变量名;
int    *ip;    //整型的指针 
double *dp;    //double 型的指针 
float  *fp;    //浮点型的指针 
char   *ch;    //字符型的指针 

指针变量的初始化:

  • &是取地址运算符,&变量名表示变量的地址。
  • 变量的数据类型必须于指针变量的数据类型一致。
  • 为了安全起见,有时会把指针初始化为空指针(NULL或0)
数据类型 *指针变量名 = &变量名;
*指针变量名 = &变量名;
int a;
int *p = &a;
int *p2;
p2 = &a;

指针变量的引用:

  • & 取地址符 * 指针运算符(间接运算符),其后是指针变量,表示该指针变量所指向的变量。
  • & *的优先级是相同的,结合方式都是自左向右。比如 &*p等价于&(*p)。
int x = 3;
int y;
int *p;
p = &x;
y = *p;//y = a

指针运算(地址运算)

算术运算(移动指针运算):加减,自增自减。
p+n运算得到的地址是p+n*sizeof(数据类型)。
两个相同数据类型的指针可以进行加减运算,一般用于数组的操作中。
关系运算:指针指向同一串连续存储单元才有意义,比如数组。与0比较,判断是不是空指针。
赋值运算:变量地址赋值给指针变量,数组元素地址赋值给指针变量,指针变量赋值给其他指针变量。

int arr[10],len;
int *p1 = &arr[2],*p2 = &arr[5];
 len = p2-p1;//arr[2] 和arr[5]之间的元素个数 3

new和delete运算符

  • new-为变量分配内存空间;
  • 可以通过判断new返回的指针的值,判断空间是否分配成功。
  • delete-释放空间
指针变量 = new 数据类型(初值);
delete 指针变量;
delete[] 指针变量;//释放为多个变量分配的地址
int *ip;
ip= new int(1);
delete ip;
int *ip;
 ip= new int[10];
 for (int i = 0; i < 10;i++)
 {
	 ip[i] = i;
 }
 delete[] ip;
int a[3][4] = {0};

5、指针与数组

 

 

6、数组与new(动态创建数组) 

一维数组

int* arr1 = new int[2];//delete []arr1;
int* arr2 = new int[3]{ 1,2 };//delete []arr2

二维数组

int m=2, n=3;
int** arr3 = new int*[2];//delete []arr3
for (int i = 0; i < 10; ++i)
{ 
	arr3[i] = new int[3]; // delete []arr3[i]
}
int* arr4 = new int[m*n];//数据按行存储 delete []arr3

7、指针与字符串

  • 字符串数组名:char ch[] = "heiren";char *p = ch;
  • 字符串:char *p = "heiren";
  • 指针赋值运算:char * p;p = "Heiren";
    指针与函数,指针可以作为函数的参数,也可以作为函数的返回值。

8、指向函数的指针

 

 9、引用

引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据,类似于window中的快捷方式。

引用不占内存空间,必须在定义的同时初始化,且不能再引用其他数据。
引用在定义时需要添加&,在使用时不能添加&,使用时添加&表示取地址
引用型变量声明:数据类型 &引用名 = 变量名;

int a;
int &b = a;//a和b表示相同的变量,具有相同的地址。

引用可以作为函数参数,也可以作为函数返回值。

void swap(int &r1, int &r2) {
    int temp = r1;
    r1 = r2;
    r2 = temp;
}
int &add1(int &r) {
	r += 1;
	return r;
}
int main()
{
	int a = 12;
	int b = add1(a);
	cout << a << "   "<<b << endl;//13  13
	return 0;
}

将引用作为函数返回值时不能返回局部数据的引用,因为当函数调用完成后局部数据就会被销毁。
函数在栈上运行,函数掉用完,后面的函数调用会覆盖之前函数的局部数据。

int &add1(int &r) {
	r += 1;
	int res = r;
	return res;
}
void test()
{
	int xx = 123;
	int yy = 66;
}
int main()
{
	int a = 12;
	int &b = add1(a);
	int &c = add1(a);
	test();//函数调用,覆盖之前函数的局部数据
	cout << a << "   "<<b <<" "<< c<<endl;//14   -858993460 -858993460
	return 0;
}

七、函数

1、函数声明与定义

  • 函数类型-函数的返回值类型;函数名-必须符合C++标识符命名规则,后面必须跟一对括号;函数体-实现函数功能的主题部分;参数列表-函数名后面的括号内,用于向函数传递数值或带回数值。
  • 函数声明中,参数名可以省略,参数类型和函数的类型不能省略。
  • 函数声明可以放在主调函数内部,放在调用语句之前;也可以放在主调函数外,如果位于所有定义函数之前,后面函数定义顺序任意,各个主调函数调用也不必再做声明
  • 当函数定义在前,函数调用灾后,可以不用函数声明。

后两条总结一下就是:调用函数前,程序得知道有这个函数,声明就是提前让程序知道有这么的玩意

函数声明:

函数类型 函数名(参数列表);
eg:
int max(int a,int b);//声明函数时,a,b可以省略
int max(int,int);
void show();

函数定义:

函数类型 函数名(参数列表)
{
	函数体;
}
eg:
int max(int a,int b)
{
	int z;
	z = a>b?a:b;
	return z;
}

2、函数参数以及返回值

  • 形参:函数定义后面括号里的参数,函数调用前不占内存。
  • 实参:函数调用括号里的参数,可以是常量,变量或表达式等。

形参和实参必须个数相同、类型一致,顺序一致函数传递方式:传值,指针,引用

 

 

 

 3、函数调用

  • 函数可以单独作为一个语句使用。有返回值的函数,可将函数调用作为语句的一部分,利用返回值参与运算。
    函数调用形式:参数传递–>函数体执行–>返回主调函数
函数名(实参列表);
show();

 函数的嵌套调用:

int a()
{
	return 666;
}
int b(int sum)
{
	return sum+a()
}
int main()
{
	cout<<b(222)<<endl;//888
	return 0;
}

 函数的递归调用:直接递归调用和间接递归调用

  • 一个函数直接或间接递归调用该函数本身,称为函数的递归调用
  • 递归和回归:原问题=>子问题 子问题的解=>原问题的解
//直接递归调用:求1+...n的值
int total(int sum)
{
	if (sum == 1)
	{
		return 1;
	}
	return sum + total(sum - 1);
}
int main()
{
	cout << "total = " << total(10) << endl;//total = 55
	system("pause");
	return 0;
}
//间接递归调用
int f2();
int f1()
{
...
  f2()
}
int f2()
{
	f1();
}

4、重载函数

5、内联函数

八、字符串(string)

1、字符串的定义和初始化

//定义
string 变量;
string str1;
//赋值
string str2 = "ShangHai";
string str3 = str2;
str3[3] = '2';//对某个字符赋值
//字符串数组
string 数组名[常量表达式]
string arr[3];

2、字符串的处理函数

#include <iostream>
#include <algorithm>
#include <string>
string str;//生成空字符串
string s(str);//生成字符串为str的复制品
string s(str, strbegin,strlen);//将字符串str中从下标strbegin开始、长度为strlen的部分作为字符串初值
string s(cstr, char_len);//以C_string类型cstr的前char_len个字符串作为字符串s的初值
string s(num ,c);//生成num个c字符的字符串
string s(str, stridx);//将字符串str中从下标stridx开始到字符串结束的位置作为字符串初值
size()和length();//返回string对象的字符个数
max_size();//返回string对象最多包含的字符数,超出会抛出length_error异常
capacity();//重新分配内存之前,string对象能包含的最大字符数
>,>=,<,<=,==,!=//支持string与C-string的比较(如 str<”hello”)。  使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得 比较,string (“aaaa”) <string(aaaaa)。    
compare();//支持多参数处理,支持用索引值和长度定位子串来进行比较。返回一个整数来表示比较结果,返回值意义如下:0:相等 1:大于 -1:
  
push_back() 
insert( size_type index, size_type count, CharT ch );//在index位置插入count个字符ch
insert( size_type index, const CharT* s );//index位置插入一个常量字符串
insert( size_type index, const CharT* s, size_type n);//index位置插入常量字符串
insert( size_type index, const basic_string& str );//index位置插入常量string中的n个字符
insert( size_type index, const basic_string& str, size_type index_str, size_type n);//index位置插入常量str的从index_str开始的n个字符
insert( size_type index, const basic_string& str,size_type index_str, size_type count = npos);//index位置插入常量str从index_str开始的count个字符,count可以表示的最大值为npos.这个函数不构成重载 npos表示一个常数,表示size_t的最大值,string的find函数如果未找到指定字符,返回的就是一个npos
iterator insert( iterator pos, CharT ch );
iterator insert( const_iterator pos, CharT ch );
void insert( iterator pos, size_type n, CharT ch );//迭代器指向的pos位置插入n个字符ch
iterator insert( const_iterator pos, size_type count, CharT ch );//迭代器指向的pos位置插入count个字符ch
void insert( iterator pos, InputIt first, InputIt last );
iterator insert( const_iterator pos, InputIt first, InputIt last );
append() 和 + 操作符
//访问string每个字符串
string s1("yuanrui"); // 调用一次构造函数
// 方法一: 下标法
for( int i = 0; i < s1.size() ; i++ )
     cout<<s1[i];
// 方法二:正向迭代器
for( string::iterator iter = s1.begin();; iter < s1.end() ; iter++)
     cout<<*iter;
 // 方法三:反向迭代器
for(string::reverse_iterator riter = s1.rbegin(); ; riter < s1.rend() ; riter++)
     cout<<*riter;
 iterator erase(iterator p);//删除字符串中p所指的字符
iterator erase(iterator first, iterator last);//删除字符串中迭代器区间[first,last)上所有字符
string& erase(size_t pos = 0, size_t len = npos);//删除字符串中从索引位置pos开始的len个字符
void clear();//删除字符串中所有字符
string& replace(size_t pos, size_t n, const char *s);//将当前字符串从pos索引开始的n个字符,替换成字符串s
string& replace(size_t pos, size_t n, size_t n1, char c); //将当前字符串从pos索引开始的n个字符,替换成n1个字符c
string& replace(iterator i1, iterator i2, const char* s);//将当前字符串[i1,i2)区间中的字符串替换为字符串s
//tolower()和toupper()函数 或者 STL中的transform算法
string s = "ABCDEFG";
for( int i = 0; i < s.size(); i++ )
     s[i] = tolower(s[i]);
transform(s.begin(),s.end(),s.begin(),::tolower);
size_t find (constchar* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t find (charc, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找字符c,返回找到的位置索引,-1表示查找不到字符
size_t rfind (constchar* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t rfind (charc, size_t pos = npos) const;//在当前字符串的pos索引位置开始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_of (const char* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_not_of (const char* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_t find_last_of(const char* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_last_not_of (const char* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,查找最后一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串
sort(s.begin(),s.end());
substr(pos,n);//返回字符串从下标pos开始n个字符
strtok()
char str[] = "I,am,a,student; hello world!";
const char *split = ",; !";
char *p2 = strtok(str,split);
while( p2 != NULL )
{
    cout<<p2<<endl;
    p2 = strtok(NULL,split);
}

九、面向对象和类

面向对象概述

1、类

类也是一种数据类型。

类的声明:

class 类名
{
	public:
		公有数据成员;
		公有成员函数;
	private:
		私有数据成员;
		私有成员函数;
	protected:
		保护数据成员;
		保护成员函数;
};

 成员函数的定义:类内,类外,类外内联函数

//类外
返回类型 类名:成员函数名(参数列表)
{
	函数体;
}
//内联函数:类外
inline 返回类型 类名:成员函数名(参数列表)
{
	函数体;
}

2、类成员的访问权限以及类的封装

C++中public、private、protected只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分
类内部没有访问权限的限制,都可以互相访问。
在C++中用class定义的类中,其成员的默认存取权限是private。

3、对象 

//1.声明类同时定义对象
class 类名
{
	类体;
}对象名列表;
//2.先声明类,再定义对象
类名 对象名(参数列表);//参数列表为空时,()可以不写
//3. 不出现类名,直接定义对象
class 
{
	类体;
}对象名列表;
//4.在堆上创建对象
Person p(123, "yar");//在栈上创建对象
Person *pp = new Person(234,"yar");//在堆上创建对象

注:不可以在定义类的同时对其数据成员进行初始化,因为类不是一个实体,不合法但是能编译运行
对象成员的引用:对象名.数据成员名 或者 对象名.成员函数名(参数列表)

4、构造函数

 构造函数名必须与类名相同

没有任何返回值和返回类型

创建对象自动调用,不需要用户来调用,且只掉用一次

类没有定义任何构造函数,编译系统会自动为这个类生成一个默认的无参构造函数
构造函数定义

//1.类中定义 2.类中声明,类外定义
[类名::]构造函数名(参数列表)
{
	函数体
}

创建对象

类名 对象名(参数列表);//参数列表为空时,()可以不写

带默认参数的构造函数

class Person
{
public:
	Person(int = 0,string = "张三");
	void show();
private:
	int age;
	string name;
};
Person::Person(int a, string s)
{
	cout<<a<<" "<<s<<endl;
	age = a;
	name = s;
}
void Person::show()
{
	cout << "age="<<age << endl;
	cout << "name=" <<name << endl;
}
int main()
{
     Person p; //0 张三
	Person p2(12);//12 张三
	Person p3(123, "yar");//123 yar
	return 0;
}

带参数初始化表的构造函数

类名::构造函数名(参数列表):参数初始化表
{
	函数体;
}
参数初始化列表的一般形式:
参数名1(初值1),参数名2(初值2),...,参数名n(初值n)
class Person
{
public:
	Person(int = 0,string = "张三");
	void show();
private:
	int age;
	string name;
};
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}

构造函数重载:构造函数名字相同,参数个数和参数类型不一样。

 拷贝构造函数

类名::类名(类名&对象名)
{
	函数体;
}
class Person
{
public:
	Person(Person &p);//声明拷贝构造函数
	Person(int = 0,string = "张三");
	void show();
private:
	int age;
	string name;
};
Person::Person(Person &p)//定义拷贝构造函数
{
	cout << "拷贝构造函数" << endl;
	age = 0;
	name = "ABC";
}
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
int main()
{
	Person p(123, "yar");
	Person p2(p);
	p2.show();
	return 0;
}
//输出
123 yar
拷贝构造函数
age=0
name=ABC

5、析构函数

 6、对象指针

对象指针的声明和使用

类名 *对象指针名;
对象指针 = &对象名;
//访问对象成员
对象指针->数据成员名
对象指针->成员函数名(参数列表)
Person p(123, "yar");
Person* pp = &p;
Person* pp2 = new Person(234,"yar")
pp->show();

 指向对象成员的指针

数据成员类型 *指针变量名 = &对象名.数据成员名;
函数类型 (类名::*指针变量名)(参数列表);
指针变量名=&类名::成员函数名;
(对象名.*指针变量名)(参数列表);
Person p(123, "yar");
void(Person::*pfun)();
pfun = &Person::show;
(p.*pfun)();

this指针
每个成员函数都有一个特殊的指针this,它始终指向当前被调用的成员函数操作的对象

class Person
{
public:
	Person(int = 0,string = "张三");
	void show();
private:
	int age;
	string name;
};
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
void Person::show()
{
	cout << "age="<<this->age << endl;
	cout << "name=" <<this->name << endl;
}

7、静态成员

以关键字static开头的成员为静态成员,多个类共享。

  • static 成员变量属于类,不属于某个具体的对象
  • 静态成员函数只能访问类中静态数据成员
    静态数据成员
//类内声明,类外定义
class xxx
{
	static 数据类型 静态数据成员名;
}
数据类型 类名::静态数据成员名=初值
//访问
类名::静态数据成员名;
对象名.静态数据成员名;
对象指针名->静态数据成员名;

静态成员函数

 8、友元

借助友元(friend),可以使得其他类中得成员函数以及全局范围内得函数访问当前类得private成员。
友元函数

友元函数不是类的成员函数,所以没有this指针,必须通过参数传递对象。
友元函数中不能直接引用对象成员的名字,只能通过形参传递进来的对象或对象指针来引用该对象的成员。

//1.将非成员函数声明为友元函数
class Person
{
public:
	Person(int = 0,string = "张三");
	friend void show(Person *pper);//将show声明为友元函数
private:
	int age;
	string name;
};
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
void show(Person *pper)
{
	cout << "age="<< pper->age << endl;
	cout << "name=" << pper->name << endl;
}
int main()
{;
	Person *pp = new Person(234,"yar");
	show(pp);
	system("pause");
	return 0;
}
//2.将其他类的成员函数声明为友元函数
//person中的成员函数可以访问MobilePhone中的私有成员变量
class MobilePhone;//提前声明
//声明Person类
class Person
{
public:
	Person(int = 0,string = "张三");
	void show(MobilePhone *mp);
private:
	int age;
	string name;
};
//声明MobilePhone类
class MobilePhone
{
public:
	MobilePhone();
	friend void Person::show(MobilePhone *mp);
private:
	int year;
	int memory;
	string name;
};
MobilePhone::MobilePhone()
{
	year = 1;
	memory = 4;
	name = "iphone 6s";
}
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
void Person::show(MobilePhone *mp)
{
	cout << mp->year << "年  " << mp->memory << "G " << mp->name << endl;
}
int main()
{
	Person *pp = new Person(234,"yar");
	MobilePhone *mp = new MobilePhone;
	pp->show(mp);
	system("pause");
	return 0;
}

友元类
当一个类为另一个类的友元时,称这个类为友元类。 友元类的所有成员函数都是另一个类中的友元成员
语法形式:friend [class] 友元类名

  • 类之间的友元关系不能传递
  • 类之间的友元关系是单向的
  • 友元关系不能被继承

十、继承和派生

1、继承

继承就是再一个已有类的基础上建立一个新类,已有的类称基类或父类,新建立的类称为派生类和子类;派生和继承是一个概念,角度不同而已,继承是儿子继承父亲的产业,派生是父亲把产业传承给儿子。

一个基类可以派生出多个派生类,一个派生类可以继承多个基类

派生类的声明:

 继承方式:

public-基类的public成员和protected成员的访问属性保持不变,私有成员不可见。
private-基类的public成员和protected成员成为private成员,只能被派生类的成员函数直接访问,私有成员不可见。
protected-基类的public成员和protected成员成为protected成员,只能被派生类的成员函数直接访问,私有成员不可见。

利用using关键字可以改变基类成员再派生类中的访问权限;using只能修改基类中public和protected成员的访问权限。

class Base
{
public:
	void show();
protected:
	int aa;
	double dd;
};
void Base::show(){
}
class Person:public Base
{
public:
	using Base::aa;//将基类的protected成员变成public
	using Base::dd;//将基类的protected成员变成public
private:
	using Base::show;//将基类的public成员变成private
	string name;
};
int main()
{
	Person *p = new Person();
	p->aa = 12;
	p->dd = 12.3;
	p->show();//出错
	delete p;
	return 0;
}

 派生类的构造函数和析构函数

  • 先执行基类的构造函数,随后执行派生类的构造函数
  • 先执行派生类的析构函数,再执行基类的析构函数。
  • 派生类的构造函数:派生类名(总参数列表):基类名(基类参数列表),子对象名1(参数列表){构造函数体;}
class Base
{
public:
	Base(int, double);
	~Base();
private:
	int aa;
	double dd;
};
Base::Base(int a, double d) :aa(a), dd(d)
{
	cout << "Base Class 构造函数!!!" << endl;
}
Base::~Base()
{
	cout << "Base Class 析构函数!!!" << endl;
}
class Person:public Base
{
public:
	Person(int,double,string);
	~Person();
private:
	string name;
};
Person::Person(int a,double d,string str):Base(a,d),name(str)
{
	cout << "Person Class 构造函数!!!" << endl;
}
Person::~Person()
{
	cout << "Person Class 析构函数!!!" << endl;
}
int main()
{
	cout << "创建Person对象..." << endl;
	Person *p = new Person(1,2,"yar");
	cout << "删除Person对象...." << endl;
	delete p;
	system("pause");
	return 0;
}

2、多继承

一个派生类同时继承多个基类的行为。

多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。

多重继承派生类声明的一般形式:

多重继承派生类的构造函数:

 二义性问题:多个基类中有同名成员,出现访问不唯一的问题。

  • 1.类名::同名成员名;
  • 2.派生类定义同名成员,访问的就是派生类同名成员。

3、虚基类

c++引入虚基类使得派生类再继承间接共同基类时只保留一份同名成员。

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class)。
派生类的 同名成员 比虚基类的 优先级更高
虚基类的声明:class 派生类名:virtual 继承方式 基类名

class  A//虚基类
{
protected:
	int a;
};
class B: virtual public A
{
protected:
	int b;
};
class C:virtual public A
{
protected:
	int c;
};
class D:public B,public C
{
protected:
	int d;
	void show()
	{
		b = 123;
		c = 23;
		a = 1;
	}
};

 如果 B 或 C 其中的一个类定义了a,也不会有二义性,派生类的a 比虚基类的a 优先级更高。

如果 B 和 C 中都定义了 a,那么D直接访问a 将产生二义性问题。
应用:c++中的iostream , istream , ostream,base_io

4、向上转型

数据类型的转换,编译器会将小数部分直接丢掉(不是四舍五入)

 只能将将派生类赋值给基类(C++中称为向上转型): 派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用
派生类对象赋值给基类对象,舍弃派生类新增的成员;派生类指针赋值给基类指针,没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向;派生类引用赋值给基类引用,和指针的一样。、
上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员

5、多态

 不同的对象可以使用同一个函数名调用不同内容的函数。

  • 静态多态性-在程序编译时系统就决定调用哪个函数,比如函数重载和静态多态性
  • 动态多态性-在程序运行过程中动态确定调用那个函数,通过虚函数实现的。

6、虚函数

在基类中不执行具体的操作,只为派生类提供统一结构的虚函数,将其声明为虚函数。

 实现程序多态性的一个重要手段,使用基类对象指针访问派生类对象的同名函数。

  • 将基类中的函数声明为虚函数,派生类中的同名函数自动为虚函数。
  • 声明形式:virtual 函数类型 函数名 (参数列表);
  • 构造函数不能声明为虚函数,析构函数可以声明为虚函数。
class  A
{
public:
	virtual void show()
	{
		cout << "A show" << endl;
	}
};
class B:  public A
{
public:
	void show()
	{
		cout << "B show" << endl;
	}
};
int main()
{
	
	B b;
	b.show();//B show
	A *pA = &b;
	pA->show();//B show 如果show方法前没用virtual声明为虚函数,这里会输出A show
	
	system("pause");
	return 0;
}

7、纯虚函数

8、抽象类 

 十一、IO流

 1、流类和对象

输入流-从输入设备流向内存的流。
输出流-从内存流出设备的流。
内存缓冲区-用来存放流中的数据。
输入输出流程:键盘输入=》键盘缓冲区=(回车触发)》程序的输入缓冲区=》‘>>’提取数据
                        输出缓冲区=(缓冲满或endl)》‘<<’送到 显示器显示
输入/输出流类:
iostream:ios ,istream,ostream,iostream
fstream:ifstream,ofstream,fstream
strstream:istrstream,ostrstream,strstream

  • istream 是用于输入的流类,cin 就是该类的对象。
  • ostream 是用于输出的流类,cout 就是该类的对象。
  • ifstream 是用于从文件读取数据的类。
  • ofstream 是用于向文件写入数据的类。
  • iostream 是既能用于输入,又能用于输出的类。
  • fstream 是既能从文件读取数据,又能向文件写入数据的类。
  • istrstream 输入字符串类
  • ostrstream 输出字符串类
  • strstream 输入输出字符串流类

 2、标准输入输出流

C++的输入/输出流库(iostream)中定义了4个标准流对象:cin(标准输入流-键盘),cout(标准输出流-屏幕),cerr(标准错误流-屏幕),clog(标准错误流-屏幕)

cerr 不使用缓冲区,直接向显示器输出信息;而输出到 clog 中的信息会先被存放到缓冲区,缓冲区满或者刷新时才输出到屏幕。
cout 是 ostream 类的对象,ostream 类的无参构造函数和复制构造函数都是私有的,所以无法定义 ostream 类的对象。
使用>>提取数据时,系统会跳过空格,制表符,换行符等空白字符。所以一组变量输入值时,可用这些隔开。
输入字符串,也是跳过空白字符,会在串尾加上字符串结束标志\0。

int  x;
double y;
cin>>x>>y;
//输入 22 66.0  两个数之间可以用空格、制表符和回车分隔数据
char str[10];
cin>>str;//hei ren  字符串中只有hei\0

输入流中的成员函数

  • get函数:cin.get(),cin.get(ch)(成功返回非0值,否则返回0),cin.get(字符数组(或字符指针),字符个数n,终止字符)

  •  getline函数:cin.getline(字符数组(或字符指针),字符个数n,终止标志字符)读取字符知道终止字符或者读取n-1个字符,赋值给指定字符数组(或字符指针)

  • cin.peek() 不会跳过输入流中的空格、回车符。在输入流已经结束的情况下,cin.peek() 返回 EOF。
  • ignore(int n =1, int delim = EOF)

  •  putback(char c),可以将一个字符插入输入流的最前面。

 输出流对象

  • 插入endl-输出所有数据,插入换行符,清空缓冲区
  • \n-输出换行,不清空缓冲区
  • cout.put(参数) 输出单个字符(可以时字符也可以是ASII码)

格式化输出
iomanip 中定义的流操作算子:

  • *不是算子的一部分,星号表示在没有使用任何算子的情况下,就等效于使用了该算子,例如,在默认情况下,整数是用十进制形式输出的,等效于使用了 dec 算子

 流操作算子使用方法:cout << hex << 12 << "," << 24;//c,18

setiosflags() 算子
setiosflags() 算子实际上是一个库函数,它以一些标志作为参数,这些标志可以是在 iostream 头文件中定义的以下几种取值,它们的含义和同名算子一样。

 多个标志可以用|运算符连接,表示同时设置。例如:

 如果两个相互矛盾的标志同时被设置,结果可能就是两个标志都不起作用,应该用 resetiosflags 清除原先的标志

 ostream 类中的成员函数:

 setf 和 unsetf 函数用到的flag,与 setiosflags 和 resetiosflags 用到的完全相同。

十二、文件操作 

文件-指存储在外部介质上的数据集合,文件按照数据的组织形式不一样,分为两种:ASCII文件(文本/字符),二进制文件(内部格式/字节)

ASCII文件输出还是二进制文件,数据形式一样,对于数值数据,输出不同

1、文件类和对象

C++ 标准类库中有三个类可以用于文件操作,它们统称为文件流类。这三个类是:

ifstream:输入流类,用于从文件中读取数据。
ofstream:输出流类,用于向文件中写人数据。
fstream:输入/输出流类,既可用于从文件中读取数据,又可用于 向文件中写人数据。
文件流对象定义:

 2、打开文件

 open函数:void open(const char* szFileName, int mode);

ios::binary 可以和其他模式标记组合使用

流类的构造函数
eg:ifstream::ifstream (const char* szFileName, int mode = ios::in, int); 

 3、文件的读写

 

 

4、文件指针移动操作

  • ifstream 类和 fstream 类有 seekg 成员函数,可以设置文件读指针的位置;
  • ofstream 类和 fstream 类有 seekp 成员函数,可以设置文件写指针的位置。
  • ifstream 类和 fstream 类还有 tellg 成员函数,能够返回文件读指针的位置;
  • ofstream 类和 fstream 类还有 tellp 成员函数,能够返回文件写指针的位置。

函数原型

ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);
//mode有三种:ios::beg-开头往后offset(>=0)字节 ios::cur-当前往前(<=0)/后(>=0)offset字节 ios::end-末尾往前(<=0)offect字节
int tellg();
int tellp();
//seekg 函数将文件读指针定位到文件尾部,再用 tellg 函数获取文件读指针的位置,此位置即为文件长度

 5、文本文件和二进制文件打开方式的区别

UNIX/Linux 平台中,用文本方式或二进制方式打开文件没有任何区别。
在 UNIX/Linux 平台中,文本文件以\n(ASCII 码为 0x0a)作为换行符号;而在 Windows 平台中,文本文件以连在一起的\r\n(\r的 ASCII 码是 0x0d)作为换行符号。
在 Windows 平台中,如果以文本方式打开文件,当读取文件时,系统会将文件中所有的\r\n转换成一个字符\n,如果文件中有连续的两个字节是 0x0d0a,则系统会丢弃前面的 0x0d 这个字节,只读入 0x0a。当写入文件时,系统会将\n转换成\r\n写入。
用二进制方式打开文件总是最保险的。

十三、泛型和模板

  • 泛型程序设计在实现时不指定具体要操作的数据的类型的程序设计方法的一种算法,指的是算法只要实现一遍,就能适用于多种数据类型,优势在于代码复用,减少重复代码的编写。
  • 模板是泛型的基础,是创建泛型类或函数的蓝图或公式

 1、函数模板

函数模板的一般形式:

template<class T>或template<typename T>
函数类型 函数名(参数列表)
{
	函数体;
}
template<class T1,class T2,...>//class可以换成typename
函数类型 函数名(参数列表)
{
	函数体;
}
//举个栗子
template<class T> T max(T a, T b)
{
	return a > b ? a : b;
}
int main()
{
	cout <<"max value is "<< max(12,34) << endl;//34
	cout << "max value is " << max(12.4, 13.6) << endl;//13.6
	cout << "max value is " << max(12.4, 13) << endl;//error 没有与参数列表匹配的 函数模板 "max" 实例参数类型为:(double, int)
	return 0;
}

 2、类模板

声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
类模板的一般形式:

当类中的成员函数在类的声明之外定义时,它必须定义为函数模板,带上模板头,定义形式如下:

3、模板的使用

 4、typename 和 class 的区别

在模板引入 c++ 后,采用class来定义模板参数类型,后来为了避免 class 在声明类和模板的使用可能给人带来混淆,所以引入了 typename 这个关键字。

模板定义语法中关键字 class 与 typename 的作用完全一样。
不同的是typename 还有另外一个作用为:使用嵌套依赖类型(nested depended name)

class MyClass
{
public:
		typedef int LengthType;
		LengthType getLength() const
		{
			return this->length;
		}
		void setLength(LengthType length)
		{
			this->length = length;
		}
		
private:
	LengthType length;
};
template<class T>
void MyMethod(T myclass)
{
	//告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量
	typedef typename T::LengthType LengthType; //
	LengthType length = myclass.getLength();
	cout << "length = " <<length<< endl;
	
}
int main()
{
	MyClass my;
	my.setLength(666);
	MyMethod(my);//length = 666
	return 0;
}

十四、命名空间和异常处理

1、命名空间

命名空间实际上是由用户自己命名的一块内存区域,用户可以根据需要指定一个有名字的空间区域,每个命名空间都有一个作用域,将一些全局实体放在该命名空间中,就与其他全局实体分割开来。
命名空间定义的一般形式:

 命名空间成员的引用:命名空间名::命名空间成员名
使用命名空间别名:namespace 别名 = 命名空间名
使用using声明命名空间成员的格式:using 命名空间名::命名空间成员名;
使用using声明命名空间的全部成员:using namespace 命名空间名;

using声明后,在using语句所在的作用域中使用该命名空间成员时,不必再用命名空间名加以限定。
标准C++库的所有标识符(包括函数、类、对象和类模板)都是在一个名为std的命名空间中定义的。
无名的命名空间,只在本文件的作用域内有效。

2、异常处理

 

 

 

十五、STL

C++标准模板库(Standard Template Library,STL)是泛型程序设计最成功的实例。STL是一些常用数据结构和算法的模板的集合,由Alex Stepanov主持开发,于1998年被加入C++标准。

C++ 标准模板库的核心包括三大组件:容器,算法,迭代器

1、容器

顺序容器:可变长动态数组Vector、双端队列deque、双向链表list
关联容器:set、multliset、map、multimap

关联容器内的元素是排序的,所以查找时具有非常好的性能。
容器适配起:栈stack、队列queu、优先队列priority_queue
所有容器具有的函数:

 顺序容器和关联容器函数:

 顺序容器独有的函数:

2、迭代器

迭代器是一种检查容器内元素并遍历元素的数据类型。C++更趋向于使用迭代器而不是下标操作,因为标准库为每一种标准容器(如vector)定义了一种迭代器类型,而只用少数容器(如vector)支持下标操作访问容器元素。按照定义方式分为以下四种。

  • 正向迭代器:容器类名::iterator 迭代器名;
  • 常量正向迭代器:容器类名::const_iterator 迭代器名;
  • 反向迭代器:容器类名::reverse_iterator 迭代器名;
  • 常量反向迭代器:容器类名::const_reverse_iterator 迭代器名

3、算法

STL 提供能在各种容器中通用的算法(大约有70种),如插入、删除、查找、排序等。算法就是函数模板。算法通过迭代器来操纵容器中的元素。
STL 中的大部分常用算法都在头文件 algorithm 中定义。此外,头文件 numeric 中也有一些算法。
许多算法操作的是容器上的一个区间(也可以是整个容器),因此需要两个参数,一个是区间起点元素的迭代器,另一个是区间终点元素的后面一个元素的迭代器。
会改变其所作用的容器。例如:

  • copy:将一个容器的内容复制到另一个容器。
  • remove:在容器中删除一个元素。
  • random_shuffle:随机打乱容器中的元素。
  • fill:用某个值填充容器。

不会改变其所作用的容器。例如:

  • find:在容器中查找元素。
  • count_if:统计容器中符合某种条件的元素的个数。
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main()  {
   vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4); //1,2,3,4
	vector<int>::iterator p;
	p = find(v.begin(), v.end(), 3); //在v中查找3 若找不到,find返回 v.end()
	if (p != v.end())
		cout << "1) " << *p << endl; //找到了
	p = find(v.begin(), v.end(), 9);
	if (p == v.end())
		cout << "not found " << endl; //没找到

	p = find(v.begin() + 1, v.end() - 1, 4); //在2,3 这两个元素中查找4
	cout << "2) " << *p << endl; //没找到,指向下一个元素4

	int arr[10] = { 10,20,30,40 };
	int * pp = find(arr, arr + 4, 20);
	if (pp == arr + 4)
		cout << "not found" << endl;
	else
		cout << "3) " << *pp << endl;
	return 0;

}

十六、更多详细教程

可以关注微 信 公 众 号 “C和C加加” 回复“ZXC”领取详细教程和更多进阶视频教程大全

猜你喜欢

转载自blog.csdn.net/weixin_55305220/article/details/118851235