零基础学C++Note

零基础学C++

文章目录

2 C++基础

-.2.1 C++程序的结构

-.2.1.4 名称空间

using 声明语句同样有生命周期,只对新标准C++头文件有效,对旧标准的头文件using 语句没有意义

using namespace std;//声明一次使得标准库中的所有名称都可用
using std::cin;//声明一次使得标准库中的cin可用
std::cin;//使用一次,声明一次
-2.1.5 C++语素

关键字

与类型有关union、violate
流程控制
存储属性register
其他asm、const_cast、dynamic_cast、mutable、reinterpret_cast、static_cast、template、typeid、typename、wschar_t

-.2.2 变量与基本类型

-2.2.1 变量
数据类型
基本数据类型
复合数据类型
int
float double
char
bool
pointer
引用
数组array
枚举enum
联合Union
结构struct
类 class
-2.2.2 整形
类型字节数表示范围
[signed] int2或4,与机器有关与机器有关
unsigned int2或4,与机器有关与机器有关
[signed] short [int]2-215-215-1
unsigned short [int]20-216-1
[signed] long [int]4-231-231-1
unsigned long [int]40-232-1

[]:可省略。1字节8位

  • 整型在内存中是用二进制补码定点存储的。
  • 从可移植的角度选择short和long类型标识整数
  • size_t 方便系统间移植。在32位系统中为unsigned int(32位) ,在64位系统中为unsigned long(64位)
类型字节数
float4
double8
long double10
-2.2.3 浮点数

每个浮点型量由符号位、阶码B、尾数C组成,大小为C*2B

-2.2.4 基本字符型

转义字符:不能打印的特殊字符

符号含义
‘\n‘换行
’\r‘回车
’\t‘水平制表符
’\v‘垂直制表符
’\b‘退格
’\f‘进制

‘’’ ‘"’ ‘\’

-2.2.5 宽字符型

cout、cin默认处理的是char型数据,不能直接用于处理wchar_t字符,iostream中提供了与此类似的wcout和wcin,专门处理wcahr_t型字符,此外可以通过前缀L指明wchar_t型常量或字符串

-2.3 常量

通过程序或者输入无法改变常量值。
使用后缀区分整型常量和浮点型常量的长度。

-2.3.1 整型常量和浮点型常量
  1. 表示一个整型常量

十进制:无前缀,int i=100;

八进制:0前缀,int i=0124;

十六进制:0x或者0X前缀,int i=0x64;

-2.3.2 字符型常量

对双字符常量来说,系统将其解释为一个int型的数据,前面的字符作为低位字节,后面的字符作为高位字节,这种方式只适合可显示字符。对有些系统来说,int为2字节,则‘ABA’和‘ABAB’不合法,对int类型占4个字节的系统来说,'ABA’和’ABAB’可用。对任何系统来说,四位以上的字母组合都是不合法的。

字符常量可由转义代码表示,反斜杠后给一个八进制或十六进制的ASCII码,用单引号括起来,表示ASCII表中对应的字符。‘A’=’\0101’=’\0x41’

-2.3.3 字符串常量

相比字符常量多’\0’

-2.3.4 符号常量

用const修饰的标识符指向一个“只读”的程序实体,称为符号常量。与普通常量的不同之处,在于其像变量一样有标识符,有效提高了系统的可修改性和可读性。

调试变量可采用此方法减少修改量

数组的长度可用此定义。

const int PEOPLE=5;//在程序中可以用PEOPLE来代表5

应在声明同时对const常量进行初始化,一旦声明,const常量无法修改。

-2.3.5 枚举常量

用户自己定义一种数据类型,并列举该数据类型的取值范围,每个枚举常量对应一个整数,一般情况下枚举常量不能计算。在对

enum 枚举类型名 {枚举常量1[=整型常数1],枚举常量2[=整型常数2],枚举常量3[=整型常数3],枚举常量4[=整型常数4],....}[变量名列表]
enum day{Sunday,Monday=10,Tuesday=10,Wednesday,Thursday,Friday=20,Saturday}curray;//Sunday=0,Monday=10,Tuesday=10,Wednesday=11,Thursday=12,Friday=20,Saturday=21

-2.4 运算符与表达式

浮点数判断等价应该是一个区间

-2.4.6 条件运算符

一个三目运算符

s1 ? s2 : s3;
-2.4.7 位运算

位逻辑运算符:&(与)、^(异或)、|(或)、~(非)
移位运算符:<<(左移) >>(右移)

A<<n;/*A左移n位*/
A>>n;/*A右移n位*/
-2.4.8 赋值运算

程序实体是内存中的一块可标识区域,左值是左值表达式的简称,是一个程序实体的表达式。判断一个表达式是否为左值的方法是看其能否放在等号[赋值号]的左边

  1. 声明时使用的“=”符号,称为初始化符,与赋值符不同。 同时声明两个变量并初始化的语句是非法的。
  2. 用const修饰的变量也是左值,但不能放在赋值号的左边,只有能被修改的左值才可以放在赋值号的左边
-2.4.9 ++和–
a++;/*不是左值*/
++a;/*是左值*/
++++a;/*合法*/
++(a++);/*非法*/
++(a+b);/*非法*/
-2.4.10 逗号表达式

用逗号分隔的一组表达式,从左到右计算,逗号表达式的结果是最右边表达式的值,其类型也是最后一个表达式的类型。

合理使用逗号表达式可以写出精炼的代码

-.2.4.11 sizeof运算符与sizeof表达式

其返回值为size_t,无符号整数,与负数比较时应转换为(int)

-2.4.12 运算符的优先级和结合性
  • 操作数多的运算符优先级低,单目>双目>三目>赋值>逗号
  • 双目优先级:算术>比较>位>逻辑
  • 算术运算符:*、/和%的优先级高于+ -
  • 位:- >& >^ >|
  • 逻辑:!>&&>||
    赋值运算符具有相同的优先级
-2.6.2 switch结构

为了程序的精炼,经常采用多个入口共用一个语句的写法

switch(x)
{
    case 1:case 3:case 5:
        cout<<"a"<<endl;
        break;
    case 2:case 4:
        cout<<"b"<<endl;
        break;
    default:
        cout<<"请检查输入是否正确"<<endl;
}
-2.6.11 自由转向语句 goto

只要在某行前注以标号,便可以使用"goto+标号"的形式将流程转到该行。能从多重循环中一下子跳到外边,避免了运用多次break的麻烦。

3 数组和C风格字符串

-3.2 一维数组

-3.2.2 初始化一维数组

对一个大数组而言,如果要在数组声明时把所有元素初始化为0,那么可以采用如“int name[1000]={0}”.

-3.2.4 数组操作注意事项

不合法行为

  1. 用一个已经初始化的数组对另一个数组赋值
  2. 对数组进行整体的输入输出
  3. 数组比较
  4. 数组整体运算

-3.3 C风格字符串

-3.3.3 get()函数和getline()函数

get()函数将换行符存入C风格字符串,getline()函数丢弃换行符。

/*getline()函数有两个参数,一个是字符数组名,
另一个是要读取的字符数。
在读取指定数目的字符(第二个参数减1)后,
或者是遇到换行符时,getline()函数停止读取。*/
cin.getline(school,50);
cin.get(数组名,数组大小);
cin.get();
//
cin.get(数组名,数组大小).get();
//
(cin.get(数组名,数组大小)).get();
//
(cin>>...).get();
-3.3.6 C风格字符串处理函数

标准头文件cstring中常见的字符串处理函数

操作函数原型备注
取得C风格字符串的长度size_t strlen(数组名)不包括空字符
复制C风格字符串char* strcpy(目标数组名,源数组名)
C风格字符串相等比较int strcmp(,)
将小写字母都转换成大写char* strupr()
将两个C风格字符串连接起来char* ==strcat(,) ==

4 指针和引用

-4.1 指针的定义与使用

-4.1.1 声明一个指针变量

指针是一种数据类型,基于该类型声明的变量称为指针变量,和普通的变量一样,在使用指针变量之前应先对指针变量进行声明。

/*类型 *指针变量名,*(指针运算符)表明语句声明的是一个指针变量,
类型指定了指针所指向的内存单元的数据类型,
该指针对于其他数据类型的内存无法访问。*/
int* pNum;
-4.1.2 初始化指针变量

在对指针变量使用间接引用符(*)前,一定对其进行初始化,在声明的同时初始化或赋值,使其有一个确定的值,对于无处可指的指针变量也要将其初始化为NULL即0.、

-4.1.3 指向指针的指针

指针变量也有地址,因此可用一个指针指向其地址,这称为指向指针的指针或二级指针。

int Num;
int *pN=&Num;
int **ppN=&pN;

-4.3 动态内存分配

在程序运行时申请一块未命名的内存用来储存变量或者更加复杂的数据结构,并把该内存的首地址记录下来。

-4.3.1 使用new 动态内存分配
类型名 *指针变量名=new 类型名;
int *pNum=new int;/*声明了指向int类型的指针pNum,并用动态申请内存的首地址为pNum同时将其进行初始化,因此,用指针pNum可访问这块内存区域*/
-4.3.2 使用delete动态释放及动态申请的内存、

动态申请的内存必须由程序员释放。

delete指令会释放动态申请的内存块,但不会删除指针本身,还可以将指针重新指向另一块内存区域。

delete 语句不能释放声明变量获得的内存。

与声明获得的数组不同,使用new动态申请数组的元素个数可变。

-4.3.6 动态申请并不一定成功
char *pC=new char[10];
if(pC!=null)
    //执行操作
    else
       //内存申请失败处理

通常在内存释放后,将指针赋值为null,这样就不会再次释放已经释放的内存。

-4.4 指针和const

-4.4.1 常量指针或指针常量

在*的右边添加const修饰符,所声明的指针称为指针常量,即该指针为常量。

int x=0;
int * const pX=&x;//pX的值无法改变,但可通过间接引用改写pX指向的变量。

声明指针常量时必须对其进行初始化,因为指针常量在声明完毕无法进行修改,因此,未初始化的指针常量是没有意义的。

-4.4.2 常量指针

将const 放在指针类型之前,使无法通过间接引用改写指针所指向的变量。

int x=5;
const int *pX=&x;

禁止间接引用改写不代表内存变量无法改写,通过变量名访问和改写内存是合法的。

-4.4.3 常量指针常量
int x=5;
const int* const pX=&x;

-4.5 指针与数组

-4.5.1 数组名指针

a[i]==*(a+i);

-4.5.3 指向数组的指针

如果数组是多维数组,有两种指针声明方式,即普通指针和数组名方式。

  1. 普通指针方式
int sz[2][3][4];
int *pz=sz[0][0];
//sz的遍历过程,多为数组一维化,对pz指针运算单位为int
for(int i=0;i<2;i++)
    for(int j=0;j<3;j++)
        for(int k=0;k<4;k++)
            cout<<*(pz+3*4*i+4*j+k);
  1. 数组名方式
int sz[2][3][4];
int (*pz)[3][4]=sz;//采用数组名方式声明指针pz,对pz指针运算单位是[3][4]的二维整型数组;
for(int i=0;i<2;i++)
    for(int j=0;j<3;j++)
        for(int k=0;k<4;k++)
            cout<<*(*(*(pz+i)+j)+k);
-4.5.4 指针数组
char *c="abcd";
cout<<c;//完成的是字符串的整体输出
cout<<*c;//仅仅输出指针所指位置的一个字符

-4.6 引用

对引用的操作与变量直接操作完全一样

-4.6.1 引用的声明

声明语句中的初始值必须是一个变量或另一个引用。一经声明,不能修改。

类型标识符 &引用名=初始值;&不是取地址符,而是“引用说明符”
-4.6.3 引用的使用限制
  1. 不能建立数组的引用
  2. 声明引用的引用是非法的
int &&refNum=num;

指向引用的指针×

int &*refPum=Num;

指向指针的引用√

int *&refPum=pNum;//pNum是一个int的指针,指针的引用合法
-4.6.4 其他(关于指针和引用)
int &refNum=*new int;//对用new 申请的动态无名实体建立一个引用
delete &refNum;//

​ 两种情况下,必须用const修饰引用

const int & refInt=9;//去掉const非法
int x;
const double &refDouble=x;//去掉const非法
  1. 对非左值的引用,如常数
  2. 类型声明符与初值类型不一致,但可转换。

const 修饰的引用是只读的无法改写,更有价值的是函数值的传递引用及调用。

5 结构体、共用体

-5.1 结构

-5.1.1 如何定义一个结构

结构体的使用分为两步,一是结构的定义,二是结构变量的声明

struct student//结构的定义
{
    char name[20];
    int age;
    float weight;
};
[struct] student A;//结构变量的声明,[]:可省略
struct student//结构的定义
{
    char name[20];
    int age;
    float weight;
} A;//结构变量的声明
struct 
{
    char name[20];
    int age;
    float weight;
} A;//结构变量的声明,没有类型名称的结构

数组可以相互赋值,利用结构来处理数组的整体赋值操作。

-5.1.5 结构体变量的sizeof

结构体变量的大小不是由组成类型大小的简单叠加,而涉及到字节对齐机制。成员变量的定义顺序也会影响结构体变量占据的内存大小。

  1. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除
  2. **每个成员相对于结构体首地址的偏移量都是成员大小的整数倍。**否则Internal Adding。复合成员相对于结构体首地址的偏移量都是复合成员中最宽简单类型成员大小的整数倍
  3. 结构体的总体大小为结构体最宽基本类型的整数倍。否则Trailing Padding

字节对齐有助于加快计算机的取数速度,节省指令周期。

-5.2 共用体

在公用体中,各种数据 类型在内存中占据相同的地址,换句话说,在某个确定的时刻,共用体只能表示一种数据类型。

-5.2.1 共用体的定义
union 共用体名称
{
    存储数据列表
};
union info
{
    int grade;
    char department[20];
};
-5.2.3 共用体变量的声明和初始化
[union] 共用体 共用体变量;
info person={2};

初始化只能对列表中第一个变量进行初始化。

6 函数

-6.2 函数的定义

方法就是接口

形参是角色,实参是演员。

返回值不能是数组,但可以是其他任何类型,如指针、结构体和共用体

-6.3 函数声明

函数声明描述了函数和编译器间的接口,想要调用一个函数,必须在调用函数中对被调用函数进行说明

在调用函数中对被调用函数进行声明,使其可用。

-6.3.2 声明一个函数

C++的函数声明只写变量类型即可。

作用:

  1. 使编译器正确处理返回值
  2. 使编译器检查输入的数目
  3. 使编译器检查输入的类型,并对相关类型进行类型隐式转换
-6.3.3 分割程序文件

将函数定义在cpp文件中,将函数声明放在同名(可以不同名但为了标识设为为同名)的h文件中,这样可以通过编译器进行编译预处理h文件实现函数声明。

头文件声明的函数中的头文件对本文件无效。

-6.4 函数调用

  1. 实参向形参传递数据
  2. 为获得数据的参数和函数中声明的变量分配临时存储空间
  3. 中断当前函数,将函数流程转到被调用函数的入口

对于值传递和指针传递来说,被调用函数将返回的值或者指针存储在CPU寄存器或某块内存区域,然后调用函数访问这块内存区域。当被调函数执行完,若返回指针指向这块内存地址已经被释放掉,访问已经释放掉的内存会给程序带来致命的问题。

-6.4.6 函数返回值
#include<iostream>
using namespace std;

int main()
{
	int *minus(int ,int);/*传指针返回*/
	long &multiply(int,int );/*传引用返回*/
	int x=2,y=3;
	int *minusans=minus(x,y);
	long *mul=&multiply(x,y);/**mul=&(*z)*/
	cout<<"2-3="<<*minusans<<endl;
	cout<<"2*3="<<*mul<<endl;
	delete minusans;
	delete mul;
	return 0;
}
int *minus(int m,int n)
{
	int *z=new int;/*z自动消亡,但p指向的int型内存空间不会释放掉,代码退出后,由主程序释放这块动态申请的内存*/
	*z=m-n;
	return z;
}
long &multiply(int m,int n )
{
	long *z=new long;
	*z=m*n;
	return *z;
}
-6.4.7 默认参数调用
  1. 参数默认必须按照从后往前的顺序
  2. 和函数声明一样,在函数调用时省略某个参数,必须省略其后所有后续参数
-6.4.8 inline函数

在调用函数前,应保护被调函数现场,记录程序当前位置以方便从被调函数中返回,恢复现场,并从原来的位置处继续执行。

对一些很短、频繁调用的函数体,系统开销无法忽视。

使用函数可以提高程序的可读性,方便阅读和修改(函数封装起来,提供接口,实现可变),用于共享。

定义inline 函数的方法,在普通函数的定义前加修饰符inline即可。

注意:

  1. 在一个文件中定义的inline 函数不能在另一个文件中使用。
  2. inline函数体中,不能有循环语句、if语句或switch语句,否则,即使函数定义时使用了inline 关键字,编译器也会将其当成普通函数来处理
  3. inline 函数应在调用和声前进行定义

-6.5 递归

任何用递归编写的函数都可用循环代替,以提高效率。但是。递归带来的好处也是显而易见。

  1. 程序的可读性比较好,易于修改和维护
  2. 在函数不断调用中,函数的规模在减小

-6.6 函数的重载

-6.6.1 何时使用函数重载

形参的类型应不同,对于形参类型相同,只有形参个数不同的场合,就无需定义两个函数,采用默认参数调用机制,只要定义一个函数即可。(对于无法满足默认函数调用机制要求的可在函数体第一行赋值)

-6.7 C++如何使用内存

c++有3种管理数据内存的方式即自动存储(栈)、静态存储和动态存储

-6.7.1 自动存储
  1. auto 关键字 (默认)
  2. register 关键字 通知编译器,用户希望通过CPU寄存器处理变量,加快变量的访问速度。

缺点:寄存器是没有内存地址的,不能对 register自动变量进行取地址操作

-6.7.2 静态存储(编译器预分配)

静态存储的变量可分为全局变量和静态变量、

  1. extern关键字 全局变量

extern 声明的变量称为全局变量,意味着可以在程序种任何位置使用,全局变量是在函数和类外定义的

全局变量存在,定义性声明和引用性声明,定义性声明用于创建变量(初始化表达式不可省略)

只要在外部定义的变量,编译器就将其当作全局变量。extern 可省略,此时初始化表达式可省略。

只要在使用全局变量前,需要对其进行引用性声明,这和函数类似,引用性声明只有一种格式。

定义在前,使用在后,定义和声明用在同一个文件中可省略引用

区别:

  1. 定义性声明只有一次,引用性声明可以有多次
  2. 定义性声明一定在外部,引用性声明没有限制
  3. 定义性声明有两种格式:带extern(不可缺少初始化语句)和省略extern的形式(可省略初始化语句,编译器默认初始化),而引用性声明只有带extern 的一种。
    2. **static关键字 ** 静态变量
  • 没有定义性声明和引用性声明之分,只有声明语句
  • 即可外部声明又可内部声明。外部声明时,只能有一个文件中使用,内部声明时所在代码块内使用。内部静态变量与自动变量唯一区别就是内部静态变量具有永久生存期
  1. 全局变量和静态变量的初始化

只能使用常量表达式来初始化全局变量和静态变量。常量表达式包括直接常量、const 常量、枚举常量和sizeof()运算符。不能用变量初始化全局变量和静态变量。全局变量和静态变量可以相互赋值,前提时变量可见。

-6.8 作用域与可见域

存在、有效、使用对应变量的生存期、作用域和可见域。

使用作用域::可使被屏蔽的全局变量在局部可见

-6.8.3 函数的作用域和可见域

使用关键字static将函数声明为内部的,只能在本文件种使用该函数,在函数定义和函数声明中都要使用static关键字。static将屏蔽其他文件种外部定义的同名函数

-7.1.2 野指针

指针被free或delete一定要置为null,指针指向的内存已被赋予新的意义。也可称为野指针的情况

  1. 未初始化指针
  2. 指向临时变量的指针[改为动态内存申请即可]
-7.1.4 用错sizeof

当数组作为函数参数进行传递时,数组退化为同类型的指针,用sizeof无法取得数组的大小。

-7.1.5 内存越界访问

写越界,即往不该写的内存地址空间中写了东西,会带来匪夷所思的错误和BUG,有些症状是随机的。

-7.1.6 养成变量初始化的习惯
-7.2.1 参数传递时的“副本”

值传递和指针传递都有“副本”。

指针传递,将指针的值复制,副本和外部指针同时指向相同的地址,若在函数中对指针值修改,则无法对外部指针产生任何影响。可以通过间接引用对指针指向的内存进行修改。

因此可以使用PointerToPointer用二级指针对指针的值进行修改。

#include<iostream>
using namespace std;
void GetMem(char **p,int num)
{
	*p=new char[num];/*对传入二级指针所指的指针的内存修改*/
	cout<<"p在内存中的地址:"<<p<<endl;
}
int main()
{
	char *pChar=NULL;
	cout<<"pChar在内存中的地址:"<<&pChar<<endl;
	GetMem(&pChar,10);/*pChar指针的地址*/
	if(pChar!=NULL)
	{
		cout<<"内存申请成功"<<endl;
		delete [] pChar;
	}
	else
	{
		cout<<"内存申请失败"<<endl;
	}
	return 0;
}

-7.3 函数与指针

-7.3.1 指向函数的指针
  1. 函数指针的声明与初始化
返回类型 (*指针名) (参数列表)
#include<iostream>
	using namespace std;
	void GetMem(char **p,int num)
	{
		*p=new char[num];/*对传入二级指针所指的指针的内存修改*/
		cout<<"p在内存中的地址:"<<p<<endl;
	}
	int main()
	{
		char *pChar=NULL;
		cout<<"pChar在内存中的地址:"<<&pChar<<endl;
		void (*a)(char **,int );/*函数指针声明*/
		a=GetMem;/*函数指针赋值*/
		a(&pChar,10);/*指针形式调用*/
		if(pChar!=NULL)
		{
			cout<<"内存申请成功"<<endl;
			delete [] pChar;
		}
		else
		{
			cout<<"内存申请失败"<<endl;
		}
		return 0;
	}
-7.3.2 typedef

给一个已经存在的类型声明一个别名

typedef int* int_ptypedef;
#define int_pdefine int * 
const int_ptypedef p1;/*声明const 指针p1,其指向可改*/
const int_pdefine p2;/*常量指针,指针可改*/
typedef void (*a)(char **,int );
a h;
h=GetMem;
h(&pChar,10);/*pChar指针的地址*/
-7.3.3 通过函数指针将函数作为另一个函数的参数
-7.3.4 函数指针数组

增强条理和可读性

-7.3.5 返回函数指针的函数

选择执行函数

-7.6 函数编写建议

-7.6.1 合理使用const

在指针传递或者引用传递时,如果参数仅仅是输入用,则应该在类型前加const,防止指针在函数体内被意外修改。

对于非内部数据类型的输入参数,应将值传递改为const 引用传递,以提高效率

8 面向对象

public成员是类的接口

-8.2.2 类的定义
  1. 数据成员的类型符前不可使用auto、extern和register,也不可在类定义时对数据成员初始化
  2. 类定义中提供的成员函数时函数的原型声明。

-8.3 C++类的实现

一种是在类定义时完成成员函数的定义,二是在类定义的外部定义其成员函数

-8.5 对象的作用域、可见域和生存周期

-8.5.3 构造函数支持重载

定义有参构造函数和无参构造函数

-8.5.4 构造函数允许按参数默认方式调用

如果仅仅是参数个数不同,推荐使用参数默认方式

-8.5.5 初始化表达式

由逗号分隔的数据成员组成,初值放在一对圆括号内。只要将成员初始化表达式放在构造函数的头体之间,并用冒号将其与函数头分隔开,可实现数据成员表达式元素的初始化

初始化表中的初始化顺序是由成员在类中被声明的顺序决定的。

-8.5.6 析构函数

默认析构函数只清除类的数据成员所占据的空间,但对类的函数成员通过new和malloc 动态申请的内存无能为力,应在类的析构函数中通过delete或free进行释放,这样能有效避免对象撤销造成的内存泄露。

-8.6 复制构造函数

系统默认一个复制构造函数,它是一个inline或public 的成员函数,其函数原型

point::point(const point &);
-8.6.2 默认复制构造函数

赋值操作后,指针指向同一内存,当一个对象析构后 ,会导致重复释放内存和野指针问题

-8.6.3 显示定义复制构造函数

如果类中含有指针型的数据成员、需要使用动态内存,程序员最好显式定义自己的复制构造函数,避免各种可能出现的内存错误。

-8.7 特殊数据成员

-8.7.1 const 数据成员

只能通过成员初始化表达式进行初始化。编译器提供的默认构造函数无法完成对const成员的初始化,而缺省的复制构造函数可以完成const成员的初始化

#include<iostream>
using namespace std;
class point
{
	const int xPos;
	const int yPos;
public:
	point(const int x=0,const int y=0):xPos(x),yPos(y)
	{
		cout<<"调用构造函数"<<endl;
	}
	void print()
	{
		cout<<"y="<<yPos<<"x="<<xPos<<endl;
	}
};
int main()
{
	point p1(3,4);
	p1.print();
	point p2(p1);
	p2.print();
	return 0;
}
-8.7.2 引用成员

只能通过成员初始化表达式来进行初始化,不可使用默认构造函数,可使用默认复制构造函数。当类中含有引用类型的数据成员(尤其是引用本对象的成员时),不要使用默认的复制构造函数,应当显式地给出复制构造函数,避免程序出现无法预料的错误。

-8.7.3 类对象成员

类对象的私有数据成员xPos和yPos都是private成员,只能在pt1、pt2初始化表达式中对类进行初始化

#include<iostream>
using namespace std;
class point
{
	const int xPos;
	const int yPos;
public:
	point(const int x=0,const int y=0):xPos(x),yPos(y)
	{
		cout<<"调用构造函数"<<endl;
	}
	void print()
	{
		cout<<"y="<<yPos<<" x="<<xPos;
	}
	point(const point&p):xPos(p.xPos),yPos(p.yPos)
	{
		cout<<"调用复制构造函数"<<endl;
	}
	~point()
	{
		cout<<"调用析构函数"<<endl;
	}	
};
class line
{
	point pt1;
	point pt2;
public:
	line(const int x1,const int y1,const int x2,const int y2):pt1(x1,y1),pt2(x2,y2)
	{
		cout<<"线的构造函数"<<endl;
	}
	line(const line &l):pt1(l.pt1),pt2(l.pt2)
	{
		cout<<"线的复制构造函数"<<endl;
	}
	void drow()
	{
		pt1.print();
		cout<<" to ";
		pt2.print();
		cout<<endl;
	}
	~line()
	{
		cout<<"线的析构函数"<<endl;
	}
};
int main()
{
	line l1(1,2,3,4);
	l1.drow();
	line l2(l1);
	l2.drow();
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QzWxlCPb-1582765995317)(零基础学C++Note.assets/image-20200220205710519.png)]

-8.7.5 static 数据成员

使用static修饰数据成员,成员在编译时就被创建并初始化(在定义性声明时被创建的)(与之相比,对象是在运行时才被创建的)

静态数据成员,应在类声明之外使用单独的定义性声明语句完成其初始化,该语句不能再使用static

初始化一定放在cpp文件中,不能放在h 文件中,因为h 文件会出现在多个cpp 文件中,出现初始化的复本,引发错误。

静态成员使用const修饰(const与static没有前后之分),而且是整型、浮点型、布尔型或枚举型,但不能是类对象、数组、引用和指针,C++允许该成员在类定义中初始化,这样,便不能在外部再次对该成员进行定义性声明,但对该成员的引用性声明是允许的。

-8.8 特殊函数成员

接口:public

内部实现:private

-8.8.1 静态成员函数

如果需要在静态函数成员内访问类的非静态成员,需要将对象引用或指向对象的指针作为参数。既可定义在类内,也可定义在类外,定义在类外不使用static。

-8.8.2 const成员函数

把const 关键字放在函数的参数表和函数体之间,表示该成员函数只能读取类的数据成员,不能修改类成员数据,const成员函数不能调用另一个非const成员函数或者改变该类的数据成员

任何不修改成员数据的函数都应该声明为const函数,这有助于提高函数的可读性和可靠性

-8.9 对象的组织

可以const对象来创建指向对象的指针和创建对象数组,还可以使用new和delete等创建动态对象。

-8.9.1 const对象

能作用于const对象的成员函数除了构造函数和析构函数外,只有const成员函数,因为const对象只能被创建、撤销以及只读访问,不许改写

-8.9.3 对象的大小

对象的大小一般是类中所有非static成员的大小之和。

  1. C++将类中的引用成员当作“指针”来维护,占4个内存字节。
  2. 类中有虚函数时(虚析构函数除外),还会分配一个指针用来指向虚函数表,因此,加4个字节
-8.9.4 this指针

编译器不会向静态成员函数传递this指针,这既是“当静态函数访问类的非静态成员时,需要将对象的引用或指向对象的指针作为参数”的原因

this指针的作用

  1. 显式指明类中数据成员,尤其是和形参以及全局变量相区别
  2. 返回本对象的指针或引用

-9.1 类的作用域

访问类的成员数据x,有两种途径:一是使用“类名::数据成员名”(也可访问嵌套类),二是使用this指针(不可使用this访问嵌套类)

使用“::x”可以在程序的任何地方访问全局变量。、

-9.2 类定义的作用域与可见域

2.类作用域

一个类可以定义在另一个类中,这是所谓的嵌套类。若类A定义在类B中,A的访问权限为public 则A的作用域可以认为和B相同,不同之处是在于必须使B::A的形式访问A的类名。当然,若A的访问权限是private,则只能在类内使用类名来创建该类的对象,无法在类外创建A类的对象。嵌套类的成员函数既可以定义为inline也可定义在类外,合理使用作用域限定符"::".

-9.3 对象的生存期、作用域和可见域

-9.3.1 先定义后实例化

不创建对象,仅仅声明一个指向类型B对象的指针

class B;/*引用性声明*/
B* pB=NULL;

-9.4 友元

可以定义一个函数、类为类的友元,则友元可以访问类的私有成员

-9.4.1 友元的非成员函数

在某个类的定义中用friend声明一个外部函数(或者其他类的函数成员,可以是public也可private)后,这个外部函数称为类的友元。

特点:

  1. 类内只需对函数进行声明,声明的位置没有要求
  2. 函数定义要放在类外,具体定义位置没有要求
  3. 友元函数不是类的成员函数,在实现时和普通函数一样,在实现时不用“::”指示属于那个类
  4. 一个函数可以同时作为多个类的友元函数
-9.4.2 友元的成员函数

当A类的成员函数作为B类的友元函数时,必须先定义A类,而不仅仅是声明它,对其实现没有具体要求。

-9.4.3 友元函数的重载

使得一组重载函数全部成为类的友元,必须一一声明,否则只有匹配的那个函数会成为类的友元,编译器仍将其他函数当作普通函数来处理。

-9.4.4 友元类

类A作为类B的友元,类A 先声明,B类定义后A类再定义。

  1. 友元关系是单向的
  2. 友元关系不具有传递性
  3. 友元关系不被继承

-9.5 运算符重载

对双目运算符来说,编译器将左边对象解释为调用对象,将右边对象解释为传递给运算符的参数。cx1+cx2等价于cx1.operator+(cx2)

complex& complex::operator ++()/*解释为对象.operator++(),其他前置单目运算符与此类似*/
{
    cout<<"前置++"<<endl;
    real+=1;
    imag+=1;
    return (*this);/*返回自身引用(*this)使得++对象 可以作为左值*/
}
complex complex::operator ++(int)/*解释为对象.operator(0),其他后置单目运算符与此类似*/
{
    cout<<"后置++"<<endl;
    complex ctemp=*this;
    ++(*this);
    return ctemp;/*以传值形式返回,不能成为左值*/
}

将操作符定义为友元函数的形式可让程序更容易实现类型的自动转换,使两个操作符都被当作函数的参数。

-9.6 运算符重载

-9.6.1 赋值运算符
class obj1=obj2;/*调用类的复制构造函数,完成obj1的创建并初始化*/
class obj1;/*先调用obj1的无参构造函数(或所有参数都有默认值的构造函数)完成obj1的创建*/
obj1=obj2;/*调用赋值运算符将obj2所有的成员的值复制到obj1*/
/*问题在于当class中包含指针指向动态内存时,会导致两个对象的指针指向同一块内存,导致其中一个对象的指针变为野指针*/
/*解决方法:将复制构造函数的特殊操作等同定义在赋值运算符重载中*/
class computer
{
    private:
    char *brand;
    float price;
    /*复制构造函数*/
    computer(const computer&p)
    {
		price=p.price;
		brand=new char[strlen(p.brand)+1];
		if(brand!=NULL)
		{
			strcpy(brand,p.brand);
		}
		cout<<"复制构造函数被调用"<<endl;
    }
	computer& operator=(const computer &p)
	{
        /*忽视的几点问题
        1.判断是否为自赋值。自己给自己赋值没有意义,如果不加判断就为指针重新申请内存,原来所指向的动态内存就泄露了
        2.释放brand所指向的内存,delete一个NULL指针是不会出现问题的。为了有效防错,delete后就立即将brand置为NULL
       */
		if(this==&p)/*如果时自赋值返回当前对象*/
		{
			return (*this);
		}
		price=p.price;
		delete [] brand;/*防止内存泄露,先释放brand(不是p.brand)指向的内存*/
		brand=new char[strlen(p.brand)+1];
		if(brand!=NULL)
		{
			strcpy(brand,p.brand);
		}
		return (*this);/*返回当前对象的引用,为的是实现链式赋值*/
	}
}
-9.6.2 函数调用运算符

函数调用运算符同样只能重载为成员函数形式

function(arg1,arg2,...)
function.operator()(arg1,arg2,...)/*其作用是将函数调用运算符()作用在对象function上,不过其参数并没有个数限制*/

一个类如果重载了函数调用符,可以将对象作为一个函数使用,这样的类的对象又称为函数对象,函数也是一种对象

#include<iostream>
using namespace std;
/*一个类如果重载了函数调用符,可以将对象作为一个函数使用,这样的类的对象又称为函数对象,函数也是一种对象*/
class Demo
{
public:
	double operator()(double x,double y);
	double operator()(double x,double y,double z);
};
 double Demo::operator()(double x,double y)
 {
	 cout<<"1:";
	 return x>y?x:y;
 }
double Demo::operator()(double x,double y,double z)
{
	cout<<"2:";
	return (x+y)*z;
}
int main()
{
	Demo de;
	cout<<de(1.0,2.3)<<endl;
	cout<<de(1.0,2.0,2.5)<<endl;
	return 0;
}
-9.6.3 下标运算符

返回引用类型很关键,这使得返回值可以作为左值

返回类型& operator[](参数类型);/*返回引用类型很关键,这使得返回值可以作为左值*/
#include<iostream>
using namespace std;
class Demo
{
	int len;
	char* pBuf;
public:
	Demo(int l)
	{
		len=l+1;
		pBuf =new char[len];
	}
	~Demo()
	{
		delete [] pBuf;
		cout<<"调用析构函数"<<endl;
	}
	int GetLen()
	{
		return len;
	}
	char& operator[](int i)
	{
		static char def='\0';
		if(i<len&&i>=0)
			return pBuf[i];
		else
		{
			cout<<"下标越界"<<endl;
			return def;
		}
	}
};
int main()
{
	Demo de(5);
	char *sz="hello";
	for(int i=0;i<strlen(sz);i++)
		de[i]=sz[i];
	for(int i=0;i<de.GetLen();i++)
		cout<<de[i];
	cout<<endl;
	return 0;
}
-9.7.1 由其他类型向定义类的转换

能接受一个参数的构造函数称为转换函数,多出来的参数必须有默认值

point(anotherpoint ap);/*p1=p2隐式转换可用*/
explicit point (anotherpoint ap);/*p1=p2隐式转换不可用,只能p1=point(p2);才能完成*/
-9.7.2 由自定义类向其他类型的转换

自定义类型是用户定义的强制类型转换函数,如何创建强制类型转换函数呢?需要在类中定义如下形式的转换函数

推荐使用显式转换

operator 目标类型名()
{
    return (目标类型的构造);
}
  1. 转换函数必须是成员函数,不能是友元函数形式
  2. 转换函数不能指定返回类型,但在函数体内必须用return语句以传值方式返回一个目标类型的变量
  3. 转换函数不能有参数
-9.8.2 完全匹配

完全匹配允许的不一致

实参形参
typetype&
type&type
type[]type*
返回类型 函数名(参数列表)返回类型 (*指针)(参数列表)
返回类型 (*指针)(参数列表)返回类型 函数名(参数列表)
typeconst type
type*const type*
typevolatile type
type*volatile type*

多个完全匹配的优选

  1. 指向非const变量或对象的指针或引用优先于const 的
  2. 非模板函数优先于模板函数

-10 继承

-10.1 继承


在任何情况下,派生类内部无法访问基类的私有成员,基类成员的初始化要通过基类的构造函数,而且,它要在派生类数据之前初始化,所以基类构造函数在派生类构造函数的初始化列表中调用

派生类生成过程:

  1. 吸收基类成员
  2. 改造基类成员
  3. 添加新的成员
-10.1.2 继承的层次性

一般来说,派生类是基类的具体化,基类抽取了派生类中的共同特征,而派生类则是对基类添加约束,使之更为具体,面向更专的领域。

在派生类中可以对基类中的某些成员进行访问,这是由派生方式和成员在基类中的访问权限决定的。·

-10.2 派生类

class 派生类名:派生方式 基类名
{
    private:
    新增私有成员列表;
    public:
    新增公开成员列表;
};

派生类可以从多个基类继承,也就是多重继承。派生类自动得到了除基类私有成员以外的其他所有数据成员和成员函数,在派生类中可以直接访问。

-10.2.2 protected成员与protected派生
  1. 在派生类中可以访问protected成员,在外部或者其他类中,protected成员和private成员一样,无法被访问。

  2. private派生使得基类中的非private成员都成为派生类中的private成员,在外部和其他类中无法访问。

  3. protected

-12 模板

-12.1.1 类型参数化
#include<iostream>
using namespace std;
template<class T>
T add(const T &a,const T &b)
{
	return a+b;
}
int main()
{
	int a=1,b=2;
	double c=1.0f,d=3.0f;
	cout<<"a+b="<<add(a,b)<<" c+d="<<add(c,d);
	return 0;
}
-12.1.2 模板的定义

模板的引入使得函数定义拜托类型的束缚,代码更加高效灵活。在C++中定义一个模板如下所示。

template<class T>
    /*or*/
template<typename T>

-12.2 函数模板

编译器根据函数模板的定义,检查传入的参数类型,生成相应的函数。并调用。

template<模板参数表>/*参数模板可以有两种类型:1.typename修饰的参数类型,代表一种类型
2.非参数类型,由已知类型符,代表一个常量表达式
返回类型和函数的参数列表中可以包含类型参数,在函数中可以使用2中的常量表达式
*/
返回值类型 函数名(参数列表)
{
    
}
-12.2.1 函数模板的使用

使用之前要进行声明

-12.2.2 隐式实例化

模板参数实例化后的函数称为模板函数

-12.2.3 Explicit Instantiation
#include<iostream>
using namespace std;
template<class T>
T add( T a, T b);
template int add<int> (int, int);/*显式实例化,<>可省略(引用类型不能?),用于解决重载导致的二义性*/
int main()
{
	int a=1,b=2;
	double c=1.0f,d=3.0f;
	cout<<"a+b="<<add(a,b)<<" c+d="<<add(c,d);
	return 0;
}
template<class T>
T add(T a, T b)
{
	return a>b?a:b;
}
-12.2.4 Explicit Specialization

解决某些类型在函数中的特殊操作。显示实例化是用模板生成某些类型参数的模板函数,特化是不使用模板,单独为某些类型生成函数定义。

template<> 返回类型 函数名[<类型实参表>](函数参数表)
{
    
}
#include<iostream>
using namespace std;
template<class T>
T add( T a, T b);
template int add(int, int);/*显式实例化*/
template<> double add(double a ,double b)/*特化,使用前定义不可声明*/
{
	return a+b;
}
int main()
{
	int a=1,b=2;
	double c=1.0f,d=3.0f;
	cout<<"a+b="<<add(a,b)<<" c+d="<<add(c,d);
	return 0;
}
template<class T>
T add(T a, T b)
{
	return a>b?a:b;
}

-12.2.5 重载

函数模板既可在模板之间重载又可在实现模板和普通函数间重载

-12.2.6 优先级和执行顺序

一般函数>特化函数>显式实例化模板函数>隐式实例化模板函数

-12.3 类模板

不管是类的定义还是成员函数的定义,都要遵守模板的定义形式

----------     stack.h       -------
    #ifndef STACK_H
#define STACK_H

#endif // STACK_H
template <typename T,int num>/*类型参数表*/
class Stack
{
    T sz[num];
    int point;
public:
    Stack();
    bool isEmpty();
    bool isFUll();
    bool push(const T&);
    bool pop(T&);
};
template <typename T,int num>
Stack<T,num>::Stack()/*模板类函数定义方式*/
{
    point=0;
}
template <typename T1,int num1>/*参数列表不要求每个字都相同,形式要相同*/
bool Stack<T1,num1>::isEmpty()/*定义方式*/
{
    return point==0;
}
template<typename T,int num>
bool Stack<T,num>::isFUll()
{
    return point==num;
}
template<typename T,int num>
bool Stack<T,num>::push(const T& obt)
{
    if(isFUll())
    {
        return false;
    }
    sz[point]=obt;
    point++;
    return true;
}
template<typename T,int num>
bool Stack<T,num>::pop( T& obt)
{
    if(isEmpty())
        return false;
    point--;
    obt=sz[point];
    return true;
}

#include <iostream>
#include"stack.h"
using namespace std;
int main()
{
    const int num=26;
    Stack<char,num> st;
    Stack<char,num> *st=new Stack<char,num>();/*动态申请内存方法*/
    cout<<"IsEmpty? "<<st.isEmpty()<<endl;
    st.push('a');
    cout<<"isEmpty? "<<st.isEmpty()<<endl;
    for(int i=1;i<num;++i)
    {
        st.push('a'+i);
    }
    cout << "isFull? " <<st.isFUll()<< endl;
    char rec=0;
    while(st.pop(rec))
        cout<<rec<<" ";
    cout<<endl;
    return 0;
}

-12.3.2 隐式实例化
Stack<int,10> *pS;//不是隐式实例化,不会隐式生成定义
-12.3.3 显式实例化
template class 类名<类型参数表>;//虽未创建类对象,但编译器已经显式生成类的定义
-12.3.4 显式特化
template<> class 类名<特殊类型>
{
    //类定义
};
#include <iostream>
#include"stack.h"
using namespace std;
template<>class Stack<double,5>
{
private:
    double sz[5];
    int point;
public:
    Stack();
    bool isNotEmpty();
};
Stack<double,5>::Stack()
{
    point=0;
}
bool Stack<double,5>::isNotEmpty()
{
    return point!=0;
}

int main()
{
    Stack<double,5> st;
    cout<<st.isNotEmpty()<<endl;
    return 0;
}

-12.3.5 部分特化

部分的显式特化

#include <iostream>
#include"stack.h"
using namespace std;
template<typename T>class Stack<T,5>
{
private:
    T sz[5];
    int point;
public:
    Stack();
    bool isNotEmpty();
};
template<typename T>/*部分特化样将未特化模板声明*/
Stack<T,5>::Stack()
{
    point=0;
}
template<typename T>
bool Stack<T,5>::isNotEmpty()
{
    return point!=0;
}

int main()
{
    Stack<double,5> st;
    cout<<st.isNotEmpty()<<endl;
    return 0;
}

重要应用为指针提供特殊版本的模板

template <class TYPE>
class Example{}
template<class TYPE*>
class Example{}

-12.4 模板的嵌套

模板的嵌套指在另一个模板中定义一个模板,以模板(类或函数)作为另一个模板(类或函数)的成员,也称成员模板


成员模板不能为virtual的

-12.4.1 函数成员模板
#include<iostream>
using namespace std;
template<class A>
class Test
{
public:
    template<typename B>
    A f(B);
};
template<typename A>
template<typename B>
A Test<A>::f(B a)
{
    return a;
}
int main()
{
    Test<int> t;
    cout<<t.f(4.6)<<endl;
    return 0;
}

-12.4.2 对象成员模板

类模板的定义可以放在另一个类中,实例化后的模板类对象可以作为另一个类的成员

#include<iostream>
using namespace std;
template<typename O>
class Outside
{
public:
    template<typename I>
    class Inside{/*嵌套类模板定义*/
    private:
        I r;
    public:
        Inside(I x)/*模板类的成员函数可以在定义时实现*/
        {
            r=x;
        }
        void disp();
    };
    Outside(O x):t(x)
    {
    }
     void disp();
private:
   Inside<O> t;
};
template<typename O>
template<typename I>
void Outside<O>::Inside<I>::disp()/*模板类的成员函数可以在定义外实现*/
{
    cout<<"Inside: "<<Outside<O>::Inside<I>::r<<endl;
}

template<typename O>
void Outside<O>::disp()
{
    cout<<"Outside:";
    t.disp();
}
int main()
{
    Outside<int>::Inside<double> obin(3.5);/*声明Inside类对象obin*/
    obin.disp();
    Outside<int> obout(2);/*创建Outside类对象obout*/
    obout.disp();
    return 0;
}

-12.5 模板参数

模板包含类型参数(class Type)和非类型参数(int NUM,NUM是常量),实际上,模板参数可以是另一个模板

#ifndef STACK_H
#define STACK_H
#endif // STACK_H
template <typename T,int num>/*类型参数表*/
class Stack
{
    T sz[num];
    int point;
public:
    Stack();
    bool isEmpty();
    bool isFUll();
    bool push(const T&);
    bool pop(T&);
    void disp()
    {
        using std::cout;
        using std::endl;
        cout<<num<<endl;
    }
};
template <typename T,int num>
Stack<T,num>::Stack()/*模板类函数定义方式*/
{
    point=0;
}
template <typename T1,int num1>/*参数列表不要求每个字都相同,形式要相同*/
bool Stack<T1,num1>::isEmpty()/*定义方式*/
{
    return point==0;
}
template<typename T,int num>
bool Stack<T,num>::isFUll()
{
    return point==num;
}
template<typename T,int num>
bool Stack<T,num>::push(const T& obt)
{
    if(isFUll())
    {
        return false;
    }
    sz[point]=obt;
    point++;
    return true;
}
template<typename T,int num>
bool Stack<T,num>::pop( T& obt)
{
    if(isEmpty())
        return false;
    point--;
    obt=sz[point];
    return true;
}

#include<iostream>
#include"stack.h"
using namespace std;
template<template<typename type,int NUM>class TypeClass,class T1,int N>/*函数模板,其类型参数表中包含一个类模板*/
void disp()
{
    TypeClass<T1,N> ob;/*对TypeClass实例化处理*/
    ob.disp();
}
int main()
{
   disp<Stack,int,8> ();
    return 0;
}

-13 STL

-13.1 STL

容器 Container

迭代器 Iterator

容器适配器 Adapter

算法 Algorithm

-13.1.1 容器 Container

容纳一些数据的模板类 vector list deque

set map multimap multiset

-13.1.2 适配器 Interface

stack queue priority_queue

-13.1.3 迭代器 Iterator

广义指针

随机访问迭代器 Random Access Iterator

双向迭代器 Bidirectional Iterator

前向迭代器 Forward Iterator

输入迭代器 Input Iterator

输出迭代器 Output Iterator

-13.2 序列式容器

vector list deque·

#include<iostream>
#include<vector>/*引用头文件*/
using namespace std;
int main()
{
   vector<int> obv;/*创建空的容器*/
   vector<int> obv1(10);/*产生特定大小的容器,容器中的元素被创建,但未被初始化,编译器采用默认值为元素初始化,对类对象调用无参构造函数*/
   vector<int> obv2(10,8);/*产生特定大小的容器,容器中的元素被创建并初始化*/
   vector<int> obv3(obv2);/*复制obv2*/
   int sz[5]={1,2,3,4,5};
   vector<int> obv4(sz,sz+5);
   /*对于vector和deque可采用以下方式随即访问,注意防止越界
list不支持以下方式,只能采用迭代器间接访问*/
   for(unsigned int i=0;i<obv4.size();i++)
       cout<<obv4[i]<<" ";
   cout<<endl;
   for(int i=0;i<(int)obv4.size();i++)
       cout<<obv4.at(i)<<" ";
   cout<<endl;
   /*创建迭代器,类似指针,指向第一个元素*/
   vector<int>::iterator iter=obv4.begin();
   /*迭代器间接访问*/
   while(iter!=obv4.end())
       cout<<(*iter++)<<" ";
   cout<<endl;
   return 0;
}
-13.2.2 所有容器支持的特征
表达式返回值说明复杂度
ob.begin()迭代器指向容器第一个元素的迭代器固定
ob.end()迭代器指向容器末尾元素的下一个迭代器固定
ob.size()size_type返回元素个数固定
ob.swap(ob2)void交换固定
ob1==ob2boolob1与ob2长度相同,且每个元素都相等时返回真线性
ob1!=ob2bool!(ob1==ob2)线性
-13.2.3 序列式容器中元素的插入和删除
  1. push_back(t) pop_back(void) //容器末插入删除元素
  2. push_front(t) pop_front(void) //vector不适用 容器内插入删除元素
  3. front(void) back(void) //返回容器的最前端和最后端元素
  4. insert插入
/*insert 3种用法*/
#include<iostream>
#include<vector>/*引用头文件*/
using namespace std;
void disp(vector<int>&t)
{
   vector<int>::iterator iter=t.begin();
   while(iter!=t.end())
       cout<<(*iter++)<<" ";
   cout<<endl;
}
int main()
{
    const int num=4;
    int sz[num]={6,7,8,9};
  vector<int> obv(sz,sz+num);/*采用数组赋值*/
  vector<int>::iterator pD=obv.end();/*创建迭代器pD*/
   /*1.将元素插入迭代器p之前,返回的迭代器指向被安插的元素*/
    /*迭代器不再指向原来位置,由于系统为容器的扩充或删除等操作造成,在使用此迭代器前,必须对迭代器重新定位*/
  pD=obv.insert(pD,1);/*在尾部插入元素1,返回的迭代器指向新插入的1*/
  disp(obv);
  /*2.将元素插入迭代器p之前,个数为num个,每个值为t,无返回值*/
  obv.insert(obv.end(),2,4);
  disp(obv);
  /*3.在迭代器p之前安插[first,last]之间的所有元素*/
  obv.insert(obv.end(),sz,sz+num);
  disp(obv);
   return 0;
}

  1. erase 删除操作 vector和deque支持下标运算符随机访问,list不能。
#include<iostream>
#include<list>/*引用头文件*/
using namespace std;
void disp(list<int>&t)
{
   list<int>::iterator iter=t.begin();
   while(iter!=t.end())
       cout<<(*iter++)<<" ";
   cout<<endl;
}
int main()
{
    const int num=4;
    int sz[num]={6,7,8,9};
  list<int> obl(sz,sz+num);/*采用数组赋值*/
  disp(obl);
  list<int>::iterator iter=obl.begin();
  iter++;
  /*抹去迭代器指向的元素,返回指向所删除元素的下一个元素的迭代器*/
  iter=obl.erase(iter);/*抹掉第2个元素,iter指向第3个元素*/
  disp(obl);
  /*抹去[first,end]之间的所有元素,返回指向被删除的一片元素的下一个元素的迭代器*/
  obl.erase(iter,obl.end());
  disp(obl);
   return 0;
}

-13.2.4 vector容器

同数组一样,在(push_back\pop_back)尾部添加删除时间固定,在中间或者头部增删(insert\erase)正比元素个数

-13.2.5 deque容器

双端队列,经常在头部删减推荐使用。

-13.2.6 list容器

双向链表,只能双向遍历

void交换固定
ob1==ob2boolob1与ob2长度相同,且每个元素都相等时返回真
ob1!=ob2bool!(ob1==ob2)
-13.2.3 序列式容器中元素的插入和删除
  1. push_back(t) pop_back(void) //容器末插入删除元素
  2. push_front(t) pop_front(void) //vector不适用 容器内插入删除元素
  3. front(void) back(void) //返回容器的最前端和最后端元素
  4. insert插入
/*insert 3种用法*/
#include<iostream>
#include<vector>/*引用头文件*/
using namespace std;
void disp(vector<int>&t)
{
   vector<int>::iterator iter=t.begin();
   while(iter!=t.end())
       cout<<(*iter++)<<" ";
   cout<<endl;
}
int main()
{
    const int num=4;
    int sz[num]={6,7,8,9};
  vector<int> obv(sz,sz+num);/*采用数组赋值*/
  vector<int>::iterator pD=obv.end();/*创建迭代器pD*/
   /*1.将元素插入迭代器p之前,返回的迭代器指向被安插的元素*/
    /*迭代器不再指向原来位置,由于系统为容器的扩充或删除等操作造成,在使用此迭代器前,必须对迭代器重新定位*/
  pD=obv.insert(pD,1);/*在尾部插入元素1,返回的迭代器指向新插入的1*/
  disp(obv);
  /*2.将元素插入迭代器p之前,个数为num个,每个值为t,无返回值*/
  obv.insert(obv.end(),2,4);
  disp(obv);
  /*3.在迭代器p之前安插[first,last]之间的所有元素*/
  obv.insert(obv.end(),sz,sz+num);
  disp(obv);
   return 0;
}

  1. erase 删除操作 vector和deque支持下标运算符随机访问,list不能。
#include<iostream>
#include<list>/*引用头文件*/
using namespace std;
void disp(list<int>&t)
{
   list<int>::iterator iter=t.begin();
   while(iter!=t.end())
       cout<<(*iter++)<<" ";
   cout<<endl;
}
int main()
{
    const int num=4;
    int sz[num]={6,7,8,9};
  list<int> obl(sz,sz+num);/*采用数组赋值*/
  disp(obl);
  list<int>::iterator iter=obl.begin();
  iter++;
  /*抹去迭代器指向的元素,返回指向所删除元素的下一个元素的迭代器*/
  iter=obl.erase(iter);/*抹掉第2个元素,iter指向第3个元素*/
  disp(obl);
  /*抹去[first,end]之间的所有元素,返回指向被删除的一片元素的下一个元素的迭代器*/
  obl.erase(iter,obl.end());
  disp(obl);
   return 0;
}

-13.2.4 vector容器

同数组一样,在(push_back\pop_back)尾部添加删除时间固定,在中间或者头部增删(insert\erase)正比元素个数

-13.2.5 deque容器

双端队列,经常在头部删减推荐使用。

-13.2.6 list容器

双向链表,只能双向遍历

-13.3 关联式容器

将值和关键字成对关联

set 仅包含关键字,没有值的概念

map 关键字-值 对

map set不会出现多个相同的关键字

multiset和multimap是map和set的扩展,允许相同关键字存在。

-13.3.1 set容器
#include<iostream>
#include<set>/*引用头文件*/
using namespace std;
void disp(set<int>&t)
{/*set不支持随机访问,必须通过迭代器方式对set容器对象中的元素进行访问*/
   set<int>::iterator iter=t.begin();
   while(iter!=t.end())
       cout<<(*iter++)<<" ";
   cout<<endl;
}
int main()
{
    const int num=5;
    int sz[num]={1,3,4,6,5};
    set<int> obs(sz,sz+num);/*默认情况下,将使用less<>模板,从小到大排序*/
    disp(obs);
   return 0;
}
-13.3.2 multiset容器
#include<iostream>
#include<set>/*引用头文件*/
using namespace std;
void disp(multiset<int>&t)
{
   multiset<int>::iterator iter=t.begin();
   while(iter!=t.end())
       cout<<(*iter++)<<" ";
   cout<<endl;
}
int main()
{
    const int num=5;
    int sz[num]={1,1,5,4,4};
    /*与set的不同之处在于其允许相同的元素*/
    multiset<int> obs(sz,sz+num);
    disp(obs);
   return 0;
}
-13.3.3 map
pair<const keyword,value> t(keydata,valuedata);
#include<iostream>
#include<map>/*引用头文件*/
#include<string>
using namespace std;
void disp(map<int,string>&t)
{
   map<int,string>::iterator iter=t.begin();
   while(iter!=t.end())
       cout<<(*iter).first<<" "<<(*iter++).second<<endl;/*first,second没有提示*/
   cout<<endl;
}
int main()
{
   typedef  pair<int,string> item;
    const int num=5;
    item t[num]={item(1,"China"),item(2,"Aemaria"),item (3,"Japan"),item(4,"German")};
    map<int,string> obM(t,t+num-1);
    /*使用迭代器构造*/
    cout<<obM.size()<<endl;
    disp(obM);
   return 0;
}
-13.3.4 muitimap

与map的关系,类似于set和multiset允许一个关键字对应多个值

-13.4 关联式容器支持的成员函数操作

-13.4.1 元素的插入

t对于set和multiset是个关键字,对于map和multimap是个pair结构

成员函数适用容器
pair<iterator,bool> ob.insert(t)map/set
iterator ob.insert(t)multimap/multiset
iterator ob.insert(p,t)map/set/multimap/multiset
void ob.insert(i,j)map/set/multimap/multiset
-13.4.2 元素的删除
  1. ob.erase(keyword) 删除ob中所有关键字为keyword的元素,并返回删除元素的个数。

为所有容器定义的类型(Type代表容器类型)

类型代表意义
Type::value_TypeType容器对象中存储的元素类型T
Type::referenceT&
Type::const_referenceconst T&
Type::iterator指向T的迭代器
Type::const_iterator指向T的const迭代器
Type::difference_type表示两个迭代器间距离的符号整数
TType::size_type无符号整型,表示容器对象的元素个数,也可作为随机访问时的下标

为关联式容器定义的类型

类型代表意义
Type::key_type关键字类型
Type::mapped_type值类型
Type::key_compare关键字比较函数对象类,默认值为less
Type::value_compare对set来说与key_compare相同,对map,为value_typep提供排序功能
  1. void ob.erase§

删除迭代器p指向的元素

  1. void ob.erase[q1,q2)

从ob删除[q1,q2)的元素

  1. void clear(void)
-13.4.3 元素的查找与访问

关联式容器元素的查找与访问

类型代表意义
iterator ob.lower_bound(k)指向第一个关键字不小于k的元素
iterator ob.upper_bound(k)指向第一个关键字大于k的元素
pairType::value_type,Type::value_type ob.equal_range(k)返回一个pair结构

-13.5 迭代器

随机访问迭代器
双向迭代器
前向迭代器
输入迭代器
输出迭代器

-13.6 STL Algorithms

在在 numeric或algorithm库中

-13.6.1 函数对象

可以以函数方式与()结合使用的任意对象

  • 函数名
  • 指向函数的指针
  • 重载()操作符的类对象

STL定义了一组函数对象,分为Arithmetic、Relational、Logical,在使用这些函数对象前,必须包含相应头文件functional .
Arithmetic:plus、minus 、negate、multiplies、divides、modules
Relational:less、less_equal、greater、greater_equal、equal_to、not_equal_to
Logical:logical_and、logical_or、logical_not

函数对象
生成器 Generator
一元函数 Unary Function
二元函数 Binary Function
一元断言-断言
二元断言
-13.6.2 算法分类

STL非修改式序列操作
STL非修改式序列操作
修改式序列操作
排序和相关操作
命名规则1

算法中 == _if == 后缀代表只有满足一定准则才生效,需要一个断言函数,输入一个元素返回bool量指示是否满足准则

count==_if==,on the other hand, accepts a range of iterators and a predicate function, then returns the number of times the predicate evaluates to true in that range

Algorithms containing the word copy (remove_copy, partial_sort_copy, etc.) will perform some task
on a range of data and store the result in the location pointed at by an extra iterator parameter.

With copy functions, you’ll specify all the normal data for the algorithm plus an extra iterator specifying a destination for the result.

If an algorithm ends in==_n== (generate_n, search_n, etc**)**, then it will perform a certain operation n times.

These functions are useful for cases where the number of times you perform an operation is meaningful,rather than the range over which you perform it.

-13.6.1 循环
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    string s="123456";
    /*1*/
    cout<<"for循环: ";
    for(int i=0;i<s.size();++i)
        cout<<s[i]<<" ";
    cout<<endl;
    /*2*/
    cout<<"迭代器: ";
    for(string::iterator it=s.begin();it!=s.end();++it)
        cout<<*it<<" ";
    cout<<endl;
    /*3*/
    cout<<"迭代器简化: ";
    for(auto it=s.begin();it!=s.end();++it)
        cout<<*it<<" ";
    cout<<endl;
    /*4*/
    cout<<"C++11特性: ";
    for(auto x:s) cout<<x<<" ";
    cout<<endl;
    return 0;
}
-13.6.2 排序

用于vector,set map自动排序

#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    string s="145623";
    sort(s.begin(),s.end());/*添加头文件,无论是否为容器都可用,基本类型也是模板的一种,用于*/
    for(auto x:s) cout<<" "<<x;
    cout<<endl;
    return 0;
}
-13.6.3 substr
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    /**begin是头迭代器,end是尾迭代器*/
    string s="5418340";
    //s=s.substr(1,3);//取418,取索引为1,往后截断3个
    s=s.substr(1,-1);//索引为1,截断到最后
    cout<<s;
    return 0;
}
-13.6.4 sstream处理字符串

istringstream的构造函数原形如下:
istringstream::istringstream(string str);
它的作用是从string对象str中读取字符,stringstream对象可以绑定一行字符串,然后以空格为分隔符把该行分隔开来。

#include <iostream>
#include<sstream>
#include<string>
using namespace std;
int main()
{
    string s="Keep coding ...";
    /*从String对象str中读取字符,stringstream可以绑定一行字符串,
以空格为分隔符把该行分隔开*/
    istringstream is(s);
    while(is)
    {
        string substr;
        is>>substr;
        cout<<substr<<endl;
    }
    return 0;
}

将流输入容器中

#include <iostream>
#include<sstream>
#include<vector>
#include<string>

using namespace std;

int main()
{
    string s="Keep coding ...";
    /*从String对象str中读取字符,stringstream可以绑定一行字符串,
以空格为分隔符把该行分隔开*/
    istringstream is(s);
    string buff;
    vector<string> vs;
    while(is>>buff)
        vs.push_back(buff);
    for(auto x:vs) cout<<x<<endl;
    return 0;
}

逆序单词

#include <iostream>
#include<sstream>
#include<stack>

using namespace std;


int main()
{
string s;
getline(cin,s);
stack<string> ss;
stringstream sBuff;
sBuff<<s;
while(sBuff>>s)
    ss.push(s);
while(!ss.empty())
{
    cout<<ss.top()<<" ";
    ss.pop();
}
cout<<endl;
    return 0;
}

字符串转数字

#include <iostream>
#include<sstream>
#include<stack>

using namespace std;


int main()
{
string s="1234";
    /*方法1*/
int i;
stringstream o;
o<<s;
o>>i;
cout<<i<<endl;
    /*方法2*/
int j=stoi(s);
cout<<i<<endl;
    return 0;
}

-13.7 适配器

适配器
容器适配器
迭代器适配器
函数适配器
stack - dequeue
queue - dequeue
priority_queue - vector
反向迭代器
插入迭代器
Negator
Binder
成员函数适配器

函数适配器的接口更简单,只是受限比一般容器要多。
迭代器适配器拓展了迭代器的功能。
函数适配器通过转换或者修改其他函数对象使得其功能得到扩展。

-13.7.1 容器适配器

在创建适配器时,通过将一个顺序容器指定为适配器的第2个类型参数,可覆盖其关联的基础容器类型。
对于给定的适配器,其关联的容器必须满足一定的约束条件

容器适配器类型关联适配器要求支持操作
stack任意一种(均提供push_back、pop_back、back)压元素入栈(push)、弹元素出栈(pop)、查看栈顶(top)、查看元素数目(size)和测试栈是否为空(empty)
queue提供pop_front(不能建立在vector容器中)队尾压入元素(push)、队首弹出元素(pop)、参看队首(front)和队尾(back)、查看元素数目(size)和测试队列是否为空(empty)[queue头函数]
priority_queue不能建立在List相对于queue增加了top操作[queue头函数]
-13.7.2 迭代器适配器

包含在iterator

  1. 反向迭代器
  2. 插入迭代器适配器
插入适配器
back_insert_iterator
front_insert_iterator
insert_iterator

back_insert_iterator(obv);
front_insert_iterator(obv);//不能基于vector创建front_insert_iterator对象
insert_iterator(obv,p);//将元素插入到其迭代器参数p指定位置的前面。
通过插入迭代器适配器可以将复制算法转化成插入算法

-13.7.3 函数适配器
  1. Binder
    bind1st(函数对象,指定值);//将指定值绑定在函数对象的第一个参数上。
    bind2nd(函数对象,指定值);//将指定值绑定在函数对象的第二个参数上。
  2. 否定器
    逆转函数对象的真伪值,要求函数对象是断言的
    not1:用于逆转一元断言
    not2:用于逆转二元断言
  3. 成员函数适配器
    容器存储元素是某个类的对象,使用成员函数适配器来调用对象的函数。
    men_fun_ref:容器存放的是对象实体。
    men_fun:容器存放的是对象指针。

-14 输入输出和文件

-14.1 输入输出

-14.1.1 文件

文件:凡是能够起到输入输出作用,与CPU直接或者间接打交道的一组信息集合。

COM1[ cluster communication port]或AUX[Auxiliary]-第一串行口,附加设备
COM2-第二串行口
CON-控制台(Console),键盘、显示器
LPT[line print terminal]1或PRT-第一并行口
NUL-空设备

对设备的输入输出称为底层输入输出

-14.1.2 流

一个流是一个由指针操作的文件或者一个物理设备,而这个指针正是指向了这个流。流相当于具有缓冲作用的接口。

-14.1.3 缓冲区

程序通常一次只能处理一个或者几个字节信息,从磁盘文件读取一个字符需要大量的操作,速度很慢,跟不上CPU的节奏。从硬盘中读取大量数据放到内存中,CPU从内存中读取数据较快,能提高输入效率。
无论缓冲区是否满,刷新操作可以立刻将缓冲区中的字节信息立刻输入或输出。

-14.1.4 重定向
-14.1.5 3种输入输出机制

1.底层I/O
调用操作系统功能对文件进行输入输出处理,具有较高的速度。打开、读写和关闭文件的一系列操作通过在io.h中的I\O函数完成。
2.高层I/O
由底层I/O的基础上扩展而来,定义在stdio.h函数中。标准I/O函数不需要考虑标准设备的打开和关闭。

标准I/O函数
字符处理函数
int getchar
int putchar
C风格字符串处理函数
char* gets
int puts
格式化处理函数
int scanf
int printf
  1. 流类库
    可以重载输入输出操作

-14.2 高层I/O

-14.2.1 printf
字符说明
c字符输出
o八进制无符号(无前导符)
p指针
u无符号十进制
x无符号十六进制
e以指数形式输出实数,默认输出6位小数
g选择%f%e中输出宽度最短的一种格式,默认6位有效数字

M -Field Width
.N -Precision
h-short修饰符
l-long修饰符
左对齐 -
非负输出+ +
空格 非负加空格
#-指定格式输出
0-填充位

-14.2.2 标准输入函数scanf
fflush(stdin);/*用以清除输入垃圾*/
-14.2.6 文件访问机制
  1. fclose-关闭一个流的函数。断开与其所有连接,并清除所有与之相联的缓冲区,释放系统分配的缓冲区,但由setbuf设置的缓冲区不能自动释放
  2. fcloseall-关闭打开的流的函数
  3. fgets-从输入流stream中读入字符存到s串中。当读到N-1个字符时,函数停止读过程,读入的最后一个字符后面加一个空字符。
  4. fopen-打开一个流函数
    fopen mode
mode意义
a打开一个文本文件,只能在文件尾部添加,如果文件不存在或者无法找到,则创建一个新文件
r+打开一个文本文件,可读可写,文件必须存在,否则fopen函数失败,返回NULL
w+打开一个文本文件,可读可写,文件存在则被重写
a+打开文件,可读可写,但只能在文件尾部添加,文件不存在,则创建一个文件

b和t放在r、w、a之后,+之前
5. fprintf-传送输出到一个流的函数
6. fscanf-格式化
7. fseek-移动文件指针
8. getc-从流中取一个字符的函数
9. getchar-从stdin流中读取一个字符的函数
10 .gets-读取一个C风格字符串的函数
10. fputs-输出一个字符到文件的函数
11. fputc-输出一个字符到文件的函数
12. putchar-输出一个字符到stdout的函数
13. puts-输出一个C风格字符串stdout的函数
14. remove-删除一个文件的函数

-14.3 流类库

流类库更安全更高效

-14.3.2 流类库的层次

iostream 类库头文件组成

类库名含义
iomanip格式化输出
ios格式设置、错误检测和状态信息
iosfwd输入输出系统使用的前置声明
sstream字符串的输入输出,基于std::string
strstream字符串的输入输出,基于c风格字符串
streambuf管理流的缓冲区

-14.4 输出流

-14.4.1 <<

  1. cs106L ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值