C++基础

C++基础部分

第一章 环境搭建

1.1 Qt Creator软件的介绍

Qt Creator是跨平台的 Qt IDE, Qt Creator 是 Qt 被 Nokia 收购后推出的一款新的轻量级集成开发环境

(IDE)。此 IDE 能够跨平台运行,支持的系统包括 Linux( 32 位及 64 位)、Mac OS X 以及

Windows。Qt Creator 的设计目标是使开发人员能够利用 Qt 这个应用程序框架更加快速及轻易的完成

开发任务。

1.2 Qt Creator软件的获取

Qt-5.8.0软件的获取(本教程使用版本)

链接:https://pan.baidu.com/s/1NeOcbw_HJuAJSx7MNMzyFg

提取码:vfrk

Qt Creator高版本获取地址

http://download.qt.io/archive/qt/

1.3 软件的安装

等待安装,需要等一小会

1.4 C++工程的创建

第二章 C++数据类型

2.1 开始进入C++

2.1.1 C++的hello world

#inlcude 头文件包含

每一个C++程序有且只有一个main函数,这是整个程序的开始位置

C++中()、[]、{}、“”、''都必须成对出现,必须都是英文符号

C++中语句要以分号结束

2.1.2 C++的注释语句

C++注释语句不参与程序的编译,在程序的预处理阶段就被编译器删除

C++注释语句一般分为行注释// 和块注释/* */

行注释:// 表示仅能注释一行

块注释:/* */可以注释多行,注意不能嵌套

2.2 关键字

#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
cout << "Hello World!" << endl;
return 0 ;
}
asmdoifreturntypedef
autodoubleinlineshorttypeid
booldynamic_castintsignedtypename
breakelselongsizeofunion
caseenummutablestaticunsigned
catchexplicitnamespacestatic_castusing
charexportnewstructvirtual
classexternoperatorswitchvoid
constfalseprivatetemplatevolatile
const_castfloatprotectedthiswchar_t
continueforpublicthrowwhile
defaultfriendregistertrue
deletegotoreinterpret_casttry

2.2.1 关键字一览表

2.2.2 数据类型相关的关键字

char 、short、int 、long 、 float、double、struct、union、enum 、signed、unsigned、void

后续的课程会逐一讲解

2.2.3 存储相关的关键字

register、static、const、auto、extern

后续的课程会逐一讲解

2.2.4 控制语句相关的关键字

if 、else 、break、continue、for 、while、do、switch case、goto、default

2.3 整型变量

2.3.1 常量和变量

常量:在程序运行过程中,其值不可以改变的量

例: 100 ‘a' “hello”
整型  100125- 1000
实型 3.140.125f-3.
字符型 ‘a',‘b',2'
字符串 “a”,“ab”,“1232

变量:其值可以改变的量被称为变量

2.3.2 整型常量

二进制:是以0b开头,如0b
十进制: 以正常数字1-9开头,如457 789
八进制: 以数字 0 开头,如 0123
十六进制:以0x开头,如0x1e
a=10,b=11,c=12, d=13,e=14,f=

注意:

c++不直接支持二进制的输入输出,bitset<8>(0b00001010)

cout默认是将数据以十进制输出,如果需要将数据以八进制、十六进制输出需要加上oct、hex

2.3.3 整型变量的定义

变量的定义形式:类型名 变量名;
data为变量名 它的类型为int类型 在 32 位平台占 4 字节空间
变量名的命名规则:由字母、数值、下划线(_)组成,不能由数值开头,不可以是关键字,区分大小写。
C++ 对于名称的长度没有限制,名称中所有的字符都有意义,但有些平台有长度限制。

案例 1 :以下变量名的命名错误的是(AD)
A:选项是变量名以数值开头,所以错误。 D选项错误的原因是将关键字void作为了变量
注意:定义变量的时候 系统会为变量开辟空间,空间大小为该变量类型的大小

变量名代表空间的内容(对变量名的操作等价于对空间的操作)

如果定义短整型变量 就是short data; //data占 2 字节

如果定义长整型变量 就是long data;//32位平台 data占 4 字节

int a= 100 ;
a= 101 ;
cout<<0b00001010<<endl;//结果为10 cout默认输出为十进制
cout<<bitset< 8 >(0b00001010)<<endl;//结果为二进制形式 00001010
注意:需要包含头文件#include<bitset>才能使用bitset
cout<< 0123 <<endl;//结果为 83 默认以十进制输出
cout<<oct<< 0123 <<endl;//结果为八进制 0123
cout<<0xab<<endl;//结果为 171 默认以十进制输出
cout<<hex<<0xab<<endl;//结果为十六进制0xab
int data;
A: int 2_num; B:int num;  C:int num_2;  D:int void;

2.3.4 整型变量的初始化

变量的初始化:在变量定义的时候,给变量赋值 叫初始化

全部变量如果不初始化内容为 0

局部变量如果不初始化内容为不确定(随机)

变量一般建议初始化为 0

2.3.5整型变量的使用

变量的使用:读(取值) 写(赋值)

变量的声明:对变量名以及类型 提前说明,不会为该变量开辟空间。

场景:先使用变量 后定义变量 必须事先对该变量进行声明

大家思考:变量的定义、变量的声明、变量的使用 三者关系

变量的定义:创建变量名 系统会为变量开辟空间

变量的声明:对变量的类型和名称 事先说明,不会为变量创建空间

变量的使用:对已经存在的变量的读写操作

int num = 10 ;//是初始化
int data;
data= 10 ;//不是初始化 仅仅是给data赋值
int data= 0 ;
int num = 0 ;
num= 100 ;//写操作
int data = 0 ;
data = num;//num读 data写
cout<<data<<endl;//读操作
data++;//data = data+1; 等号右边的data读 等号左边的data写
//提前:变量声明 需要加extern修饰
extern int data;
void test03()
{
//先使用
cout<<"data="<<data<<endl;
}
//后定义
int data = 0 ;

2.3.6 键盘给变量赋值

使用关键字cin给变量获取键输入,cin代表的输入设备
同时给多个变量获取键盘输入(每个变量的值 以空格隔开)
cin可以自动跳过空格以及回车。

2.3.7 键盘获取两个int数值 求最大值(案例)

2.4 字符类型

另一种整型:char类型。顾名思义,char类型是专为存储字符( 如字母和数字)而 设计的。现在,存储数字对于计算机来说算不 什么,但 存储字母则是另一回事。编程语言通过使用字母的数值编码解决了

这个问题。因此,char类型是另一种整型。它足够长,能够表示目标 计算机系统中的所有基本符号— 所有的字母、数字、标点符号等。实际上,很多系统支持的字符都不超过 128 个,因此用一个字节就可以表示所有的符号。因此,虽然 char 最常被用来处理字符,但也可以将它用做比short更小的整型。

下图为:ASCII表

int data = 0 ;
cin>>data;//data获取键盘输入
注意:cin与>>结合 而 cout与<<结合
int data = 0 ;
int num = 0 ;
cin>>data>>num;

2.4.1字符常量

字符常量:用单引号括起来,如:‘a’、‘b’、'0’等。注意单引号只能作用于一个字符(转义字符除外)

案例 1 :以下不能够表示字符常量的是 __ D__

输出字符和字符的ASCII值的方式:

A:'a' B:'1' C:'#' D:'123'
cout<<'a'<<endl;//输出的是字符
cout<<(int)'a'<<endl;//输出的是字符的ASCII值

2.4.2字符变量

用char定义,每个字符变量被分配一个字节的内存空间字符值以ASCII码的形式存放在变量的内存单元中。
注:
ch= ‘x’;
ch变量中存放的是字符’x’的ASCII :120,即ch=120跟ch='x’在本质上是一致的.

字符变量的初始化

注意:‘\0’和‘0’、数值 0 的区别
‘\0’字符常量(char) ASCII为 0
数值 0 是整型常量(int) 内存的存储值 0
'0’字符常量(char)ASCII 为 48

2.4.3 键盘给字符变量赋值

2.4.4 字符案例-大小写转换

需求:键盘输入一个字符 如果是大写就改成小写 如果是小写就改成大写 其他字符不转换

2.4.5 字符常量和字符串常量的区别

字符串常量

是由双引号括起来的字符序列,如“CHINA”、”哈哈哈”

“C program”,“$12.5”等都是合法的字符串常量.

字符串常量与字符常量的不同

‘a’为字符常量,”a”为字符串常量

char ch;
char ch='\0';
char ch='\0';
cin>>ch;
//cin会判断ch的类型为char 一次只能获取一个字符
//cin会跳过起始的空格和回车
'a' 97 'b'  98 'c'  99 ......  'z' 122
'A' 65 'B'  66 'c'  67 ......  'Z' 90
char ch = 'a';
ch = ch-('a'-'A');
char ch = 'A';
ch = ch+('a'-'A');

每个字符串的结尾,编译器会自动的添加一个结束标志位’\0’,

即“a”包含两个字符‘a’和’\0’

2.5 实型(浮点数)

浮点数能够表示带小数部分的数值,如:2.5 3.14 122.22.

计算机将这样的值分成两部分存储。一部分表示值,另一 部分用于对值进行放大或缩小。如数值

34.1245可以看成0.341245(基准值)和 100 (缩放因子)。缩放因子的作用是移动小数点 的位置,术

语浮点因此而得名。

C++ 内部表示浮点数的方法与此相同,只不过它基于的是二进制数,因此缩放因子是 2 的幂,不是 10 的

幂。幸运的是,程序员不必详细 了解内部表示。重要的是,浮点数能够表示小数值、非常大和非常小的

值,它们的内部表示方法与整数有天壤之别。

实型分为:单精度浮点数(float 4字节) 是双精度浮点数(double 8字节)

2.5.1 实型常量

不以f结尾的实型常量为double类型 如:3.14

以f结尾的实型常量为float类型 如:3.14f

指数形式: 123e3 代表 123*10 的三次方 123e-3

2.5.2 实型变量

单精度实型变量:float f; 占 4 字节 以f结尾的实型常量初始化

双精度实型变量:double d;占 8 字节 不以f结尾的实型常量初始化

2.6 有符号数和无符号数

2.6.1 有符号数

数据二进制的最高位为符号位 其他位为数据位。

最高位为 1 表示负数

最高位为 0 表示正数

float f=0.0f;
double d=0.0;

以一字节为例:xddd dddd

1111 1111~1000 0000~0000 0000~0111 1111

-127 ~-0 ~+0 ~+127

将-0看成-128

-128~127

2.6.2 无符号数

没有符号位 所有二进制位都是数据位

0000 0000 ~1111 1111

0 ~ 255

2.6.3 定义有符号变量

2.6.4 定义无符号变量

2.7 进制间的转换

2.7.1 进制的概述

二进制:0~1 以0b开头 bitset<8>输出

八进制:0~7 以 0 开头 0123 oct输出

十进制:0~9 123 cout默认输出十进制

十六进制:0~9 a~f 以0x开头 0x12 hex输出 不区分正负数

n进制:0~n-1

2.7.2 十进制转二进制、八进制、十六进制(短除法)

//方式一:默认方式(推荐)

int num;//num为有符号数
//方式二:使用关键字signed显示说明
signed int num;
unsigned int num;

案例 1 : 123 转成二进制 0b0111 1011

案例 2 : 123 转成八进制 0173

案例 3 : 123 转成十六进制 0x7b

2.7.3 二进制、八进制、十六进制转十进制(位次幂)

案例 1 :将二进制数 1100 0011 转换成十进制

1 2^7+1 2^6+1 2^1+1 2^0=195

案例 2 :将 0123 转换成十进制 —>83

案例 3 :将 0x12 转换成十进制 —>18

2.7.4 二进制转八进制

从右往左:每 3 位二进制 对应 1 位八进制

1101 1010 ---->0332

11 011 010

3 3 2

2.7.5 二进制转十六进制

从右往左:每 4 位二进制 对应 1 位十六进制

1101 1010 ---->0xda

1101 1010

d a

0x123456–>占 3 字节

2.7.6 八进制 转 二进制

1 位八进制 对应 3 位二进制

0123—> 001 010 011

2.7.7 十六进制 转 二进制

1 位十六进制 对应 4 位二进制

0x1d3c—>0001 1101 0011 1100

2.7.8 八进制 转 十六进制(没有直接方式)

八进制 ---->二进制---->十六进制

2.7.9 十六进制 转 八进制(没有直接方式)

十六进制 ---->二进制---->八进制

案例:0x123---->(八进制)0443

注意:不同的进制 仅仅是数据的不同表现形式而已

2.8 原码、反码、补码

2.8.1 原码、反码、补码的概述

计算机存储的是数据的补码。

原码:计算机中对数字的二进制定点表示方法

123:原码0111 1011

无符号数:

补码反码原码

123 原码:0111 1011

123 反码:0111 1011

123 补码:0111 1011

有符号数:

正数:

补码反码原码

+123原码:0111 1011

+123反码:0111 1011

+123补码:0111 1011

负数:

反码==原码符号位不变 其他位按位取反。

补码==反码+1

-123原码:1111 1011

-123反码:1000 0100

-123补码:1000 0101

负数在计算机以补码的方式存储

非负数在计算机以原码的方式存储

2.8.2 补码的意义

1 、统一了 0 的编码

2 、将减法运算变加法运算

假如:没有补码 10-6

有补码 10-6

2.8.3 对数据的存

负数在计算机以补码的方式存储

非负数在计算机以原码的方式存储

八进制数 以原码存储

  • 0 补码: 0000 0000
  • 0 补码: 0000 0000

10 : 0000 1010

  • 6 : 1000 0110

1001 0000 ---->- 16 结果有问题

10 : 0000 1010

  • 6 : 1111 1010

0000 0100 ----> 4

十六进制 以原码存储

2.8.4 对数据的取

如果是对 无符号变量 进行取值,输出内存的原样数据。

如果是对 有符号变量 进行取值,系统会去看内存的最高位,如果最高位为 0 表明正数, 内存原样 输

出。

系统会去看内存的最高位,如果最高位为 1 表明负数, 将内存数据求补码(得到原码) 输出

2.9 其他关键字

2.9.1 const修饰普通变量

如果以常量初始化 const修饰的只读变量 那么 只读变量的值 事先存放在“符号常量表中” 不会立即给data

开辟空间

当对data取地址时 系统才会为data开辟空间

short data1 = - 10 ;//补码存储
cout<<bitset< 16 >(data1)<<endl;//1111111111110110
short data2 = 6 ;//原码存储
cout<<bitset< 16 >(data2)<<endl;//0000000000000110
short data3 = 0x8080;//原码存储
cout<<bitset< 16 >(data3)<<endl;//1000000010000000

//先将-10转成无符号数(-10的补码)

unsigned short data = - 10 ;//原码1000 0000 0000 1010 补码1111 1111 1111 0110
cout<<bitset< 16 >(data)<<endl;//1111 1111 1111 0110
cout<<"data="<<data<<endl;//65526==1111 1111 1111 0110
short data = 10 ;//原码0000 0000 0000 1010
cout<<bitset< 16 >(data)<<endl;//0000 0000 0000 1010
cout<<"data="<<data<<endl;//10
short data=0x8080;//1000 0000 1000 0000
cout<<bitset< 16 >(data)<<endl;//1000 0000 1000 0000
cout<<"data="<<data<<endl;//-32640 =1111 1111 1000 0000
//const修饰data为只读变量 data的本质是变量
//只读变量 只能被初始化 不能被赋值
const int data= 100 ;
data = 10 ;//err

如果以变量初始化 const修饰的只读只读变量 那么只读变量会立即开辟空间(没有符号常量表)

const修饰自定义类型变量 立即开辟空间 (没有符号常量表)

2.9.2 register修饰寄存器变量

如果变量 别高频繁使用 会自动将变量存储在寄存器中 目的:提高访问效率

如果用户想将变量 直接放入寄存器中 可以加register修饰

2.9.3 volatile 关键字

强制访问内存

防止编译器优化

2.9.4 sizeof测量类型的大小

const int data= 100 ;
int *p = (int *)&data;
*p = 2000 ;
cout<<"*p = "<<*p<<endl;//2000
cout<<"data = "<<data<<endl;//100
int a = 10 ;
const int data = a;//以变量初始化data
int *p = (int *)&data;
*p = 2000 ;
cout<<"*p = "<<*p<<endl;//2000
cout<<"data = "<<data<<endl;//2000
register int data= 0 ;//data将放入寄存器中
//尽量不要对寄存器变量取地址
&data;
//register修饰的变量 只是尽量放入寄存器中
volatile int data= 0 ;//对data的访问 必须冲内存访问
cout<<sizeof('a')<<endl;//1B
cout<<sizeof( 10 )<<endl;//4B
cout<<sizeof(short)<<endl;//2B
cout<<sizeof(long)<<endl;//32位平台 4B
cout<<sizeof(float)<<endl;//4B
cout<<sizeof(double)<<endl;//8B

2.9.5 typedef给已有的类型重新取个别名

不能创建新类型。

将长且复杂的类型名 取一个短小的名称。

typedef作用的步骤:

1 、先用 已有的类型 定义一个普通的变量

2 、用别名 替换 变量名

3 、在整个表达式最前方 加typedef

案例 1 :给int arr[5]取个别名

案例 2 :给int *取个别名

2.10 转义字符

2.10.1 \和某些字符 结合 产生新的字符 就叫转义字符

2.10.2 八进制转义

‘\ddd’ 每个d的范围必须是0~7 3个d表示最多识别 3 位八进制数据

以下字符描述正确的是 _A

//INT32就是int类型的别名
typedef int INT32;
INT32 data;
int num;//已有的类型任然有效
typedef int MYARRAY[ 5 ];
MYARRAY arr;
typedef int *MYP;
MYP p;//int *p; p的类型就是int *类型

‘\0’ == ASCII 为 0

'\n' == 换行符
'\t' == tab缩进符
'\r' ==回到行首符号
'\a' ==发出警报

A:‘\123’ B:‘\18’ C:‘\1234’ D:‘\183’

2.10.3 十六进制转义

‘\xhh’ 每个h的范围0~9 a~f 2个h表示最多识别 2 位十六进制

以下字符描述正确的是BD

2.11 类型转换

数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题。

转换的方法有两种:

自动转换:

遵循一定的规则,由编译系统自动完成.

强制类型转换:

把表达式的运算结果强制转换成所需的数据类型

2.11.1 自动类型转换原则

1 、占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低。

2 、转换方向

无符号和有符号 参加运算 需要将有符号 转换成无符号

int和double参加运算 会将int转成从double类型

A:'\af' B:'\123' C:'\x3df' D:'\xab'
int data1 = - 10 ;
unsigned int data2 = 6 ;
if(data1+data2> 0 )
{
cout<<">0"<<endl;//结果输出
}
else
{
cout<<"<=0"<<endl;
}

char和short类型 只要参加运算 都会将自己转换成int类型

2.11.2 强制类型转换

(类型说明符) (表达式)

功能:

把表达式的运算结果强制转换成类型说明符所表示的类型

例如:

不管是自动类型转换 还是强制类型转换 都是临时。

第三章 运算符

3.1 运算符

用算术运算符将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式运算对

象包括常量、变量、函数返回值等

例如: a* b / c-1.5 + ‘a’

3.2 算术运算符

int data1 = 0 ;
double data2 = 0.0;
cout<<sizeof(data1+data2)<<endl;//8
char ch='a';
short sh= 0 ;
cout<<sizeof(ch+ch)<<endl;//4
cout<<sizeof(sh+sh)<<endl;//4
cout<<sizeof(ch+sh)<<endl;//4
(int)p+ 1 对p强转成int类型 然后再+ 1
(int)(p+ 1 )对p+ 1 强转成int类型
float f=3.14f;
int x = 0 ;
x = (int)f;
cout<<"x ="<<x<<", f="<<f<<endl;//x =3, f=3.14
      • / % += -= *= /= %=

3.2.1 /的取整

如果/的所有运算对象都是整数 /的功能就是取整

3.2.2 /的除法运算

如果/有一个运算对象是实型 /的功能就是除法运算

3.2.3 %取余运算符

案例 1 :键盘输入一个数 判断概述能否被 3 整除

案例 2 :如果rand()函数产生一个随机数>0, 请使用rand()产生60~100的随机数

案例 3 :如果rand()函数产生一个随机数>0, 请使用rand()产生’a’~'z’的随机字母

3.3 复合运算符

+= -= *= /= %=

注意:一定要将=号的右边看成一个整体

案例 1 :以下表达式执行完 a的值是

3.4 关系运算符

5 / 3 == 1 5 / 2 == 2

5 /2.0 == 2.5

5 % 2 == 1 3 % 5 == 3

n% 3 == 0 , 1 , 2
data%n == 0 , 1 , 2 ,......n- 1
a+=b;//a=a+b;
a*=b;//a=a*b;
a/=b;//a=a/b;
a%=b;//a=a%b;
int a = 3 ;
a*= 4 + 5 ;//a = a*(4+5)
a的值是: 27
int a = 12 ;
a+= a-= a*=a;

>、<、==、>=、<=、!=

3.5 逻辑运算符

3.5.1 &&逻辑与:

A && B A为真 且 B为真 整个表达式结果才为真。

A && B A或B只要有一个为假 整个表达式结果才为假。

注意:逻辑与&&的短路特性:

如果A为假 整个表达式为假, 那么B的真假决定不了整个表达式的结果,所以不会再判断B的真假,

就叫“短路特性”

3.5.2 ||逻辑或:

A || B A和B只要有一个为真 整个表达式结果为真。

A || B A和B同时假 整个表达式结果为假。

注意逻辑或||的短路特性:

如果A为真 整个表达式为真, 那么B的真假决定不了整个表达式的结果,所以不会再判断B的真假,

就叫“短路特性”

3.5.3 !逻辑非:

!真 == 假 !假==真

在c语言中 除了 0 为假 其他都为真

3.6 产生随机数

3.7 位运算(二进制位运算)

3.7.1 & 按位与

语法:全 1 为 1 有 0 为 0

特点:和 1 相与保持不变 和 0 相与为 0

场景: 将指定位 清 0

案例 1 :data为 1 字节 将data的第3,4为清 0 其他位保持不变

&& || !

! 10 == 0 !- 10 == 0! 0 == 1

1100 0011

& 1111 0000


1100 0000

3.7.2 | 按位或

语法:有 1 为 1 全 0 为 0

特点:和 1 或置 1 , 和 0 或 保持不变

场景:将指定位 置 1

案例 1 :data为 1 字节 将data的第5,6为置 1 其他位保持不变

3.7.3 ~按位取反

语法: 0 变1 1变 0

~1100 0011 == 0011 1100

3.7.4 ^按位异或

语法:相同为 0 不同为 1

特点:和 1 异或取反 和 0 异或保持不变

场景:将指定位 发生翻转

3.7.5 左移<<:左边丢弃 右边补 0

移动的位数 不要超过 自身位的宽度

unsigned char data;
data = data & 1110 0111 ;
data = data & 0xe7;//ok
data &= 0xe7;//ok

1100 0011

| 1111 0000


1111 0011

data = data | 0110 0000
data = data | 0x60;//ok
data |= 0x60;//ok

1100 0011

^ 1111 0000


0011 0011

^ 1111 0000


1100 0011

3.7.6 右移>>:右边丢弃 左边补 0 (补 1 )

算术右移、逻辑右移 都是编译器决定,用户无法确定。

无符号数:右边丢弃 左边补 0

有符号数:

正数:右边丢弃 左边补 0

负数:右边丢弃 左边补 0 (逻辑右移)

负数:右边丢弃 左边补 1 (算术右移)

编写代码测试 编译器为那种右移:

3.7.7 高级应用

案例 1 :data为 1 字节 将data的第3,4为清 0 其他位保持不变。

案例 2 :data为 1 字节 将data的第5,6为置 1 其他位保持不变

如果:data= 0000 0001 data=data<< 0 ;  data= 0000 0001 == 1 ==data* 2 ^0
如果:data= 0000 0001 data=data<< 1 ;  data= 0000 0010 == 2 ==data* 2 ^1
如果:data= 0000 0001 data=data<< 2 ;  data= 0000 0100 == 4 ==data* 2 ^2
如果:data= 0000 0001 data=data<< 3 ;  data= 0000 1000 == 8 ==data* 2 ^3
......
如果:data= 0000 0001 data=data<< 6 ;  data= 0100 0000 == 64 ==data* 2 ^6
如果:data= 1000 0000 data=data>> 0 ;  data= 1000 0000 == 128 ==data除以2^0
如果:data= 1000 0000 data=data>> 1 ;  data= 0100 0000 == 64 ==data除以2^1
如果:data= 1000 0000 data=data>> 2 ;  data= 0010 0000 == 32 ==data除以2^2
如果:data= 1000 0000 data=data>> 3 ;  data= 0001 0000 == 16 ==data除以2^3
......
如果:data= 1000 0000 data=data>> 6 ;  data= 0000 0010 == 2 ==data除以2^6
data= data & 1110 0111
1110 0111 == ~( 0001 1000 ) == ~( 0001 0000 | 0000 1000 )
==~( 0000 0001 << 4 | 0000 0001 << 3 )
== ~(0x01<< 4 | 0x01<< 3 );
data &= ~(0x01<< 4 | 0x01<< 3 );//推荐

案例 3 :data为 1 字节 将data的第3,4位清0, 5,6置 1 其他位保持不变

3.8 三目运算符?:

表达式 1 ? 值1:值 2

如果表达式 1 为真 整个表达式的值为值 1 (引用)

如果表达式 1 为假 整个表达式的值为值 2 (引用)

3.9 运算符优先级

优先级别数值越小,优先级越高,同一优先级 看结合性

data = data | 0110 0000
0110 0000 == 0100 0000 | 0010 0000 == 0000 0001 << 6 | 0000 0001 << 5
== 0x01<< 6 | 0x01<< 5
data |=(0x01<< 6 | 0x01<< 5 );//推荐
data = data & ~(0x01<< 3 |0x01<< 4 ) | (0x01<< 5 |0x01<< 6 );
cout<<( 10 > 20? 10 : 20 );
int data1 = 10 ;
int data2 = 20 ;
//(data1>data2?data1:data2) 结果等价于data2
(data1>data2?data1:data2) = 200 ;
cout<<"data1 = "<<data1<<endl;//10
cout<<"data2 = "<<data2<<endl;//200

3.10 自增自减运算符

3.10.1 自增自减独立作为一条语句

不管是i++ i-- ++i --i 如果是独立的一条语句 那么i++和++i, i–和–i没区别

3.10.2 ++在右边 先使用 后加减

int i = 3 ;
//i++;//i=i+1
++i;//i=i+1
cout<<i<<endl;//4
int i= 3 ;
int j = 0 ;
j = i++;//j=i; i++;
cout<<i<<" "<<j<<endl//4 3

3.10.3 ++在左边 先加减 后使用

第四章 控制语句

程序默认时顺序执行,但是实际项目中需要选择、循环控制。

4.1 选择控制语句 if

4.1.1 if语句

如果条件成立执行大括号里的所有语句,不成立的话大括号里的语句不执行.

注意:如果只在乎项目的某个结果,请选择上面的if语句形式

案例 1 :判断data能被 3 整除

4.1.2 if…else…语句

如果表达式 1 为真 执行语句 1 ,否则执行语句 2 。

注意:如果项目有两种结果,不会同时出现,就选择if…else…

案例 1 :键盘输入一个int数据 求它对 2 的余数

int i= 3 ;
int j = 0 ;
j = ++i;//i++; j=i;
cout<<i<<" "<<j<<endl;//4 4
if(条件表达式)
{//复合语句,若干条语句的集合
语句 1 ;
语句 2}
if(条件表达式)//如果if没有{} 只能作用一条语句(if只识别语句 1 )
语句 1 ;
语句 2 ;
int data= 12 ;
if(data% 3 == 0 )
{
cout<<data<<"能被 3 整除"<<endl;
}
if(表达式 1 )
{
语句 1 ;
}
else
{
语句 2 ;
}

4.1.3 if…else if…else语句

如果表达式 1 为真执行语句 1 ,其他条件将不会再判断。只有当表达式 1 为假时 才会去判断表达式 2 的真

假。

如果表达式 2 为真执行语句 2 ,其他条件将不会再判断。只有当表达式 2 为假时 才会去判断表达式 3 的真

假。(依此类推)

只有所有表达式都为假时,最后才会执行else中的语句n。

注意:如果项目有多个结果,但是只会出现一个,请选择if…else if

案例 1 :键盘输入一个int数据求它对 3 的余数

int data = 0 ;
cin>>data;
if(data% 2 == 0 )
{
cout<<data<<"对 2 的余数为0"<<endl;
}
else
{
cout<<data<<"对 2 的余数为1"<<endl;
}
if(表达式 1 )
{
语句 1 ;
}
else if(表达式 2 )
{
语句 2 ;
}
else if(表达式 3 )
{
语句 3 ;
}
else
{
语句n;
}
int data = 0 ;
cin>>data;
if(data% 3 == 0 )
{
cout<<data<<"对 3 的余数为0"<<endl;
}
else if(data% 3 == 1 )
{
cout<<data<<"对 3 的余数为1"<<endl;
}
else if(data% 3 == 2 )
{

4.2 选择控制语句 switch

4.2.1 switch语句形式

将表达式的结果 与 case后的常量表达式的值 一一对比,相等就从当前case语句处 进入执行 ,直到遇到

break跳出switch语句。

案例 1 :键盘输入1~7的数值 判断是星期几

cout<<data<<"对 3 的余数为2"<<endl;
}
switch(表达式)//表达式只能是字符型(char)或整型的(short int int long int)
{
case 常量表达式 1 :
语句 1breakcase 常量表达式 2 :
语句 2 ;
breakdefault:
语句 3break;
}
int date = 0 ;
cout<<"请输入1~7的数值:";
cin>>date;
if(date< 1 || date> 7 )
{
cout<<"输入的时无效数值,请输入1~7的数值"<<endl;
return;//函数返回值结束
}
switch(date)
{
case 1 :
cout<<"星期一"<<endl;
break;
case 2 :
cout<<"星期二"<<endl;
break;
case 3 :
cout<<"星期三"<<endl;
break;
case 4 :
cout<<"星期四"<<endl;
break;
case 5 :
cout<<"星期五"<<endl;
break;
case 6 :
cout<<"星期六"<<endl;
break;
case 7 :

4.3 循环控制语句 for

4.3.1 for循环语句

初始化语句:只会在进入for循环前执行一次初始化动作

循环条件:每次循环都会执行,只有当循环条件为真时 才会进入循环体

步进语句:每次循环体执行完成后 会自动执行步进语句

案例 1 :for循环求1~100的和

4.3.2 for循环中的break,contiune

break,跳出当前循环

continue,进入下一次循环

cout<<"星期日"<<endl;
break;
}
for(初始化语句;循环条件;步进语句)
{//复合语句
循环体;
}
int i= 0 ;
int sum;
//for求1~100的和
for(i= 1 ,sum= 0 ; i<= 100 ; i++)
{
sum = sum+i;
}
cout<<"sum = "<<sum<<endl;//5050
int i= 0 ,sum= 0 ;
for(i= 0 ; i<= 100 ; i++)
{
if(i== 50 )
break;
sum += i;
}
sum只加了1~49

4.3.3 循环嵌套循环

先写内层循环 然后再写外层循环

案例 1 :输出九九乘法表

4.4 循环控制语句 while

int i= 0 ,sum= 0 ;
for(i= 0 ; i<= 100 ; i++)
{
if(i== 50 )
continue;
sum += i;
}
上述代码的结果:sum== 5000
for(i= 0 ;i< 10 ;i++)
{
for(j= 0 ;j< 10 ;j++)
{
语句 1}
}
int i= 0 ;
for(i= 1 ;i<= 9 ;i++)
{
int j= 0 ;
for(j= 1 ;j<=i;j++)
{
cout<<j<<"*"<<i<<"="<<j*i<<" ";
}
cout<<endl;
}

4.4.1 while循环语句

案例 1 :while求1~100的的和

4.4.2 while循环中的break,continue

break跳出当前循环

continue直接进入下次循环

//外部实现 初始化

while(循环条件)
{
循环语句;
//内部实现 步进语句
}
int i= 1 ;
int sum = 0 ;
while(i<= 100 )
{
sum += i;//sum = sum+i;
i++;
}
cout<<sum<<endl;//5050
int i= 1 ;
int sum = 0 ;
while(i<= 100 )
{
if(i == 50 )
break;
sum += i;//sum = sum+i;
i++;
}
cout<<sum<<endl;//1~49的和
nt i= 1 ;
int sum = 0 ;
while(i<= 100 )
{
if(i == 50 )
continue;
sum += i;//sum = sum+i;
i++;
}
cout<<sum<<endl;//没有结果 while是死循环

4.4.3 do…while

案例 1 :do…while求1~100的和

如果知道循环次数 建议使用for

如果不知道循环次数 但是知道退出条件 建议使用while

4.5 goto 跳转语句

第五章 数组

5.1 一维数值数组

5.1.1 一维数值数组的概述

用一段连续空间 存放相同类型的变量 这样的容器(结构)叫数组。(重要)

数组的下标是从 0 开始。

假如数组有n个元素:

下标范围:0~n-1

元素范围:arr[0]~arr[n-1]

案例 1 :以下对int arr[5]的数组元素 访问正确的是:A

do
{
//循环体;
}while(循环条件);
先执行一次循环体 再判断循环条件 来决定 是否下一次循环
int i= 1 ;
int sum = 0 ;
do
{
sum += i;
i++;
}while( i<= 100 );

5.1.2 一维数值数组的定义

1 、定义数组的步骤:

数组名 和 [] 表示数组

将数组的个数 放入[]里面

用元素的类型 定义一个变量

从上 往下 替换。

案例 1 :定义一个数组 有 5 个元素 每个元素为int

案例 2 :定义一个数组 有 5 个元素 每个元素为int *

案例 3 :定义一个数组 有 5 个元素 每个元素为数组,该数组有 10 个元素每个元素为int

案例 4 :定义一个数组 有 5 个元素 每个元素为函数的入口地址,该函数有两个int型形参,int返回值类型

不管几维:数值数组 必须逐个元素访问。

A:arr[ 1 ]  B:arr+ 1 C:arr[ 5 ] D:arr
int arr[ 5 ];
int *arr[ 5 ];
int arr[ 5 ] [ 10 ];
int (*arr[ 5 ])(int, int);

5.1.3 一维数值数组的初始化

1 、全部元素 初始化

注意:如果数组的全部元素 都初始化 可以省略[]的数值

如果省略了[]里面数值 数组的元素个数 就由初始化的元素个数确定

2 、部分元素 初始化

未被初始化的部分 自动补 0

3 、建议将数组的所有元素初始化为 0

4 、指定下标初始化

5.1.3 一维数值数组的元素操作

1 、元素的操作 对元素的读或写

必须逐个元素操作

数组的每个元素 等价于 变量。 arr[1] == num

int arr[ 5 ];
int n = sizeof(arr)/sizeof(arr[ 0 ]);
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cout<<arr[i]<<" ";//未初始化的局部数组元素 内部不确定
}
cout<<endl;
int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
int arr[]={ 10 , 20 , 30 , 40 , 50 };
int arr[ 5 ]={ 10 , 20 , 30 };//10 20 30 0 0
int arr[ 5 ]={ 0 };//将第 0 个元素初始化为 0 其他未被初始化自动补 0 推荐
int arr[ 5 ]={ 10 };//10 0 0 0 0
int arr[ 5 ]={[ 2 ]= 10 , [ 4 ]= 30 };//0 0 10 0 30
int arr[ 5 ]={ 0 };
int n = sizeof(arr)/sizeof(arr[ 0 ]);
//num取值
cout<<arr[ 2 ]<<endl;
//num赋值 num=10
arr[ 2 ] = 10 ;

2 、键盘给数组元素获取输入

3 、案例 1 :键盘输入 10 个int数 求这 10 个数的最大值和最小值

//data = num
arr[ 3 ] = arr[ 2 ];
//num++
arr[ 3 ]++;//arr[3] = arr[3]+1
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cout<<arr[i]<<" ";//0 0 10 11 0
}
cout<<endl;
int arr[ 5 ]={ 0 };
int n = sizeof(arr)/sizeof(arr[ 0 ]);
cout<<"请输入"<<n<<"个int数值:";
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cin>>arr[i];
}
for(i= 0 ;i<n;i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
int arr[ 10 ]={ 0 };
int n = sizeof(arr)/sizeof(arr[ 0 ]);
//获取键盘输入
cout<<"请输入"<<n<<"个int数值:";
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cin>>arr[i];
}
//求最大值和最小值
int min=arr[ 0 ],max=arr[ 0 ];
for(i= 1 ;i<n;i++)
{
//进行比较
max = max>arr[i]?max:arr[i];
min = min<arr[i]?min:arr[i];
}
cout<<"min="<<min<<" max="<<max<<endl;

5.2 二维数值数组

5.2.1 二维数值数组的概述

5.2.2 二维数值数组的初始化

1 、分段初始化

2 、连续初始化

案例 1 :以下代码的结果是__ 11 __

//完全初始化

int arr[ 3 ][ 4 ]={ { 1 , 2 , 3 , 4 }, { 5 , 6 , 7 , 8 }, { 9 , 10 , 11 , 12 } };
//如果完全初始化 只能省略行数
int arr[][ 4 ]={ { 1 , 2 , 3 , 4 }, { 5 , 6 , 7 , 8 }, { 9 , 10 , 11 , 12 } };
//部分初始化
int arr[ 3 ][ 4 ]={ { 1 , 2 }, { 5 , 6 }, { 9 , 10 , 11 } };

/完全初始化

int arr[ 3 ][ 4 ]={ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 };
//如果完全初始化 只能省略行数
int arr[][ 4 ]={ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 };
//部分初始化
int arr[ 3 ][ 4 ]={ 1 , 2 , 5 , 6 , 9 , 10 , 11 };
int arr1[ 3 ][ 4 ]={ { 1 , 2 }, { 5 , 6 }, { 9 , 10 , 11 } };
int arr2[ 3 ][ 4 ]={ 1 , 2 , 5 , 6 , 9 , 10 , 11 };
arr1[ 1 ][ 2 ] +arr2[ 1 ][ 2 ] == 11

5.2.3 二维数值数组的元素操作

1 、二维数值数组 获取键盘输入

2 、求出每个人的平均成绩

nt arr[ 3 ][ 4 ]={ 0 };
int row = sizeof(arr)/sizeof(arr[ 0 ]);
int col = sizeof(arr[ 0 ])/sizeof(arr[ 0 ][ 0 ]);
int i= 0 ,j= 0 ;
for(i= 0 ;i<row;i++)
{
for(j= 0 ;j<col;j++)
{
cin>>arr[i][j];
}
}
cout<<"------------"<<endl;
for(i= 0 ;i<row;i++)
{
for(j= 0 ;j<col;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}
int arr[ 5 ][ 4 ]={{ 56 , 75 , 78 , 89 },{ 89 , 98 , 76 , 67 },{ 88 , 99 , 77 , 66 },{ 67 , 78 , 89 , 90 },
{ 98 , 97 , 96 , 95 }};
int row = sizeof(arr)/sizeof(arr[ 0 ]);
int col = sizeof(arr[ 0 ])/sizeof(arr[ 0 ][ 0 ]);
float scoreAvg[ 5 ]={0.0f};

5.3 一维字符数组

5.3.1 一维字符数组的初始化

1 、逐个元素初始化(不推荐)

2 、以字符串的方式 初始化 一维字符数组(推荐)

"“描述的是字符串, 比如字符串"hello”, 编译器会自动在字符串的末尾 添加’\0’字符 作为字符串的结束标

3 、以上两种初始化的区别

5.3.2 字符数组的遍历

使用循环方式逐个遍历(逐个字符操作)

int i= 0 ;
for(i= 0 ;i<row;i++)
{
//求和
int j= 0 ;
float sum = 0 ;
for(j= 0 ;j<col;j++)
{
sum += arr[i][j];
}
//求平均成绩
scoreAvg[i] = sum/col;
}
for(i= 0 ;i<row;i++)
{
cout<<scoreAvg[i]<<" ";
}
cout<<endl;
char arr[ 5 ]={'h','e','l','l','o'};
char arr[ 6 ]="hello";
char arr[ 6 ]="hello";
int i= 0 ;
for(i= 0 ;i< 6 ;i++)
{
cout<<arr[i];
}

使用cout直接输出字符串,需要字符数组的数组名,遇到’\0’才结束。(遍历字符串)

5.3.3 键盘获取字符串

1 、cin获取字符串 遇到空格或回车 结束输入

2 、cin.getline获取带空格的 字符串

5.3.4 一维字符数组的案例

1 、获取一个字符串 求该字符串的长度(不使用strlen)

5.4 二维字符数组

char arr1[]="hello";
cout<<arr1;//hello
char arr2[]="hel\0lo";
cout<<arr2;//hel
char str[ 32 ]="";
cout<<"请输入一个字符串:";
cin.getline(str,sizeof(str));
//获取字符串的长度
int i= 0 ;
while(str[i] != '\0')
i++;
cout<<str<<"的长度:"<<i<<endl;
char str[ 128 ]="hello";
char arr[ 5 ][ 128 ]={"hello","world", "hehehe", "xixixi", "lalala"};

5.4.1 遍历二维字符数组

5.4.2 二维字符数组获取键盘输入(获取多个字符串)

第六章 函数

6.1函数的介绍

6.1.1 函数的概述

函数是c语言的功能单位,实现一个功能可以封装一个函数来实现。

定义函数的时候一切以功能为目的,根据功能去定函数的参数和返回值

需要传哪些数据给函数?(实参)

函数的功能代码(函数体)如何实现?

函数需要返回啥类型的数据?(函数的返回值类型)

6.1.2 函数的分类(定义角度)

库函数(c++语言库)、自定义函数、系统调用(内核提供给用户的函数接口)

6.1.3 函数的分类(有无参数)

无参的函数:不能将函数外部的数据 传递 给函数内部

有参的函数:通过参数 将函数外部的数据 传递到函数内部

(参数:函数外部数据 到 函数内部的 桥梁)

6.2 函数定义、声明、调用

char arr[ 5 ][ 128 ]={"hello","world", "hehehe", "xixixi", "lalala"};
int row = sizeof(arr)/sizeof(arr[ 0 ]);
int i;
for(i= 0 ;i<row;i++)
{
cout<<arr[i]<<endl;
}
char arr[ 5 ][ 32 ]={""};
int row = sizeof(arr)/sizeof(arr[ 0 ]);
//获取键盘输入
int i;
for(i= 0 ;i<row;i++)
{
cin>>arr[i];
}
//输出二维字符数组的字符串
for(i= 0 ;i<row;i++)
{
cout<<arr[i]<<endl;
}

6.2.1 函数的定义

实现函数体,确定函数名,函数的形参、函数的返回值类型

6.2.1 函数声明

一般先调用函数 后定义函数 需要提前函数声明。

函数声明:告知编译器 该函数的函数名是啥 有几个形参 返回值类型是啥

6.2.1 函数的调用

执行函数体,一般调用格式是:函数名(实参);

6.3 函数的调用过程

//函数的定义 定义处x y叫形参 (函数定义的时候 形参不会开辟空间)
//只有当函数调用的时候 才会为形参 开辟空间 保存实参的值
int my_add(int x, int y) //x = data1, y=data2
{
//return 返回函数运算结果 结束当前函数
return x + y;
}
/函数声明 告知编译器 如果遇到函数名为my_add,有两个int形参,以及一个int返回值类型
//请通过编译
//int my_add(int, int);//ok 不推荐
int my_add(int x, int y);
#include<iostream>
using namepace std;
//函数声明 告知编译器 如果遇到函数名为my_add,有两个int形参,以及一个int返回值类型
//请通过编译
//int my_add(int, int);//ok 不推荐
int my_add(int x, int y);
int main(int argc, char *argv)
{
int data1 = 10 ;
int data2 = 20 ;
//函数的调用 data1,data2实参
int ret = 0 ;
ret = my_add(data1, data2);
cout<<"结果为:"<<ret<<endl;
}
//函数的定义 定义处x y叫形参
int my_add(int x, int y) //x = data1, y=data2
{
//return 返回函数运算结果 结束当前函数
return x + y;
}

大量调用函数 会发生 “出入栈”的开销

6.4 函数的传参

6.4.1 普通变量作为函数的参数

函数内部 需要使用外部变量的值 需要将外部变量的 值传递 给函数内部。这时 普通变量 就要作为函数

的 形参 (单向传递之值传递)

6.4.2 数组作为函数的参数

1 、数值数组作为函数的参数

函数内部 可以 操作(读写) 外部数组的元素。

#include <iostream>
using namespace std;
void inputIntArray(int arr[], int n );
void sortIntArray(int arr[], int n);
void printIntArray(int arr[], int n);
int main()
{
int arr[ 5 ]={ 0 };
int n = sizeof(arr)/sizeof(arr[ 0 ]);
//获取键盘输入
inputIntArray(arr, n);
//对数组排序
sortIntArray(arr, n);
//对数组遍历
printIntArray(arr, n);
}
void printIntArray(int arr[], int n)
{
int i= 0 ;
for(i= 0 ;i<n;i++)

2 、字符数组作为函数的参数

{

cout<<arr[i]<<" ";
}
cout<<endl;
}
void sortIntArray(int arr[], int n)
{
int i= 0 ;
for(i= 0 ;i<n- 1 ;i++)
{
int j= 0 ;
for(j= 0 ;j<n-i- 1 ;j++)
{
if(arr[j]>arr[j+ 1 ])
{
int tmp = arr[j];
arr[j] = arr[j+ 1 ];
arr[j+ 1 ] = tmp;
}
}
}
return;
}
void inputIntArray(int arr[], int n )
{
cout<<"请输入"<<n<<"个int数据:";
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cin>>arr[i];
}
return;
}
#include <iostream>
using namespace std;
void myGetString(char str[], int n);
int myStrlen(char str[]);
int main()
{
char str[ 128 ]="";
//键盘获取字符串
myGetString(str, sizeof(str));
//测量字符串的长度
cout<<"长度:"<<myStrlen(str)<<endl;;
}
int myStrlen(char str[])
{
int i= 0 ;
while(str[i] != '\0')
i++;
return i;

第七章 预处理

7.1 内存分区

进程:可执行文件 从运行到结束 整个动态的过程 就叫进程。(占内存空间)

在 32 位平台 每一个进程 占4G空间(虚拟空间)

7.2 变量的存储

7.2.1 普通局部变量

定义形式:在{}里面定义的 普通变量 叫普通局部变量

作用范围:所在的{}复合语句之间有效

生命周期:所在的{}复合语句之间有效

存储区域:栈区

注意事项:

}

void myGetString(char str[], int n)
{
cout<<"请输入一个字符串:";
cin.getline(str, n);
}
void func()
{//复合语句
int num = 0 ;//num 局部变量
{
int data = 0 ;//data 局部变量
}
}

1 、普通局部变量 不初始化 内容 不确定

2 、普通局部变量 同名 就近原则(尽量别同名)

7.2.2 普通全局变量

定义形式:在函数外定义的普通 变量

作用范围:当前源文件以及其他源文件 都有效。

生命周期:整个进程。

存储区域:全局区

注意事项:

1 、全局变量不初始化 内容为 0

2 、全局变量 和 局部变量 同名 优先选择局部变量。

3 、其他源文件 使用全局变量 必须对全局变量 进行extern声明。(变量的使用所在的源文件)extern 声

明外部可用。该变量或函数 来自于其他源文件。

01_fun.cpp

01_code.cpp

void func()
{ //复合语句
int num = 10 ; //num 局部变量
{
int num = 20 ; //data 局部变量
cout<<num<<endl;//20 就近原则
}
cout<<num<<endl;//10
}
int data = 10 ;//普通全局变量
void func()
{
}
int data = 10 ; //普通全局变量
void func()
{
int data = 20 ;//普通局部变量
cout<<data<<endl;//20
cout<<::data<<endl;//10
}
//extern 声明data为int类型 来之其他源文件
extern int data;
void add_data(void)
{
data = data+ 100 ;
return ;
}

7.2.3 静态局部变量

定义形式:在{}加static定义的局部变量 就叫静态局部变量

作用范围:所在的{}复合语句之间有效

生命周期:整个进程有效

存储区域:全局区

注意事项:

1 、静态局部变量不初始化 内容为 0

2 、静态局部变量 整个进程 都存在(第一次定义有效)

7.2.4 静态全局变量

定义形式:在函数外 加static修饰定义的变量 就是静态全局变量

extern void add_data(void);
int data = 10 ; //普通全局变量
int main()
{
add_data();
cout<<"data="<<data<<endl;
}
void test04()
{
int data1 = 10 ;//普通局部变量
static int data2 = 20 ;//静态局部变量
}
void fun04()
{
static int num=10;
num++;
cout<<"num = "<<num<<endl;
}
int main()
{
fun04();//num = 11
fun04();//num = 12
fun04();//num = 13
fun04();//num = 14
}
int data3 = 10 ;//普通全局变量
static int data4 = 20 ;//静态全局变量
void test05()
{
}

作用范围:只能在当前源文件使用 不能在其他源文件使用。

生命周期:整个进程

存储区域:全局区

注意事项:

1 、静态全局变量不初始化 内容为 0

2 、静态全局变量 只能在当前源文件使用

7.3 全局函数和静态函数

7.3.1 全局函数(函数默认 都为全局函数)

全局函数:在当前源文件 以及其他源文件 都可以使用

如果其他源文件使用需要 extern对全局函数 进行声明

7.3.2 静态函数(加static修饰的函数)

静态成员 只能在当前源文件 用

7.4 头文件包含

头文件包含:在预处理 结果 将头文件的内容 原封不动的 包含在 目的文件中

#include <head.h> 建议<>包含系统头文件

<>从系统指定目录 寻找head.h头文件

#include “head.h” 建议""包含自定义头文件

“”先从当前目录 寻找head.h头文件 如果找不到 再到系统指定的目录下寻找

static void func(void)
{
}

7.5 #define 宏

编译四阶段:预处理、编译、汇编、链接

使用关键字 define 定义 叫宏

在预处理结果 使用3.14 替换所有出现PI的位置 (宏展开)

注意:不要再宏后加;分号

宏尽量大写和普通变量区分开

7.5.1 不带参数的宏

宏的作用范围:是从定义处开始 到 当前文件结束 都有效

#undef可以结束宏的作用域

宏 没有作用域的限制 只在当前源文件 有效

7.5.2 带参数的宏(宏函数)

1 、宏的参数不能有类型

#define PI 3.14 (宏定义)
#define PI 3.14;
if(PI>3.0)
{
语句;
}
#define PI 3.14 (宏定义)
#define MY_STR "hello world"
#define N 100
#define MY_MUL(a, b) a*b
cout<<MY_MUL( 10 , 20 );//10*20
#define MY_MUL(int a, int b) a*b //error

2 、宏不能保证参数的完整性

MY_MUL(10 + 10, 20 + 20)的结果是 230 而不是 800

可以使用()的形式 让带参数的宏 具备一定的完整性

3 、宏不能作为结构体、类的成员

4 、案例:

7.5.3 宏函数和普通函数的区别

函数有作用域的限制,可以作为类的成员

宏函数没有作用限制,不能作为类的成员

第八章 指针

8.1 指针变量

8.1.1 内存的概述

在 32 位平台,每一个进程 拥有4G的空间。

系统为内存的每一个字节 分配一个 32 位的地址编号(虚拟地址)。这个编号称之为地址。

#define MY_MUL(a, b) a *b
cout<<MY_MUL( 10 , 20 ); //10*20
cout<<MY_MUL( 10 + 10 , 20 + 20 ); //10 + 10*20 + 20 == 230
#define MY_MUL(a, b) a *b
#define MY_MUL2(a, b) ((a) * (b))
cout<<MY_MUL( 10 , 20 ); //10*20
cout<<MY_MUL( 10 + 10 , 20 + 20 ); //10 + 10*20 + 20 == 230
cout<<MY_MUL2( 10 + 10 , 20 + 20 );//((10 + 10) * (20 + 20))==800
#define MY_MUL(a, b) a *b
#define MY_MUL2(a, b) ((a) * (b))
cout<<MY_MUL(MY_MUL2( 10 + 10 , 20 + 20 ), MY_MUL( 10 + 10 , 20 + 20 ));//8220

无论什么类型的地址,都是存储单元的编号,在 32 位平台下都是 4 个字节,即任何类型的指针变量都是 4

个字节大小。

8.1.2 地址和指针变量的关系

地址 就是内存的地址编号。

指针变量:本质是变量 只是该变量 保存的是 内存的地址编号。(不是普通的数值)

8.1.3 指针变量的定义

1 、定义步骤(定义的时候)

*修饰指针变量p。

保存谁的地址 就先定义谁。

从上往下整体替换。

案例 1 :

2 、在 32 位平台任何类型的指针变量 都是 4 字节

定义一个指针变量p 保存 int num的地址; int *p;
定义一个指针变量p 保存数组int arr[ 5 ]首地址; int (*p)[ 5 ]
定义一个指针变量p 保存函数的入口地址 int fun(int,int);  int (*p)(int,int);
定义一个指针变量p 保存结构体变量的地址 struct stu lucy; struct stu *p;
定义一个指针变量p 保存指针变量int *q的地址 int **p

3 、指针变量和普通变量建立关系

8.1.4 指针变量的初始化

指针变量 在操作之前 必须指向合法的地址空间。

1 、指针变量 如果不初始化 立即操作 会出现段错误

2 、指针变量 如果没有指向合法的空间 建议初始化为NULL

不要操作 指向NULL的指针变量

3 、将指针变量 初始化为 合法的地址(变量的地址、动态申请的地址、函数入口地地址)

int num = 10 ;
int *p;
p = &num;//普通变量和指针变量建立关系
cout<<*p;//10
int *p;
cout<<*p<<endl;
int *p = NULL;//NULL是赋值给p int *p; p = NULL;
int num = 10 ;
int *p = &num;//int *p; p = &num;

8.1.5 指针变量的类型

1 、指针变量自身的类型: 将指针变量名拖黑,剩下的类型就是指针变量自身的类型

指针变自身的类型 一般用于 赋值语句的判断

案例 1 :int num=10, *p=&num, **q=&p;以下结果正确的是 ABC

2 、指针变量指向的类型(重要)

将指针变量名和离它最近的一个* 一起拖黑,剩下的类型就是指针变量指向的类型

3 、指针变量的指向类型 决定了取值宽度

int data= 10 , *p=&data;//data为int类型 p为int *类型
int *p,data;//p为int *类型 data为int类型
int *p;  p自身的类型为int *
int num = 10 ;
int *p = &num;
//在使用中
//num 为int &num 为int * ---->对变量名 取地址 整体类型加一个*
//p 为int * *p 为int ---->对指针变量 取* 整体类型减一个*
//在使用中 &和*相遇 从右往左 依次抵消
*&p == p
int num = 10 ;
int *p = &num;
int **q = &p;
A:*p = 100 B: *q = &num C:p=&num D:q=&num
int *p;  p指向的类型为int
int num = 0x01020304;
int *p = &num;
为啥 *p == num == 0x01020304?

4 、指针变量的指向类型 决定了+1跨度

8.1.6 综合案例分析

案例 1 :取出0x0102的值

案例 2 :取出0x02的值

案例 3 :取出0x0203的值

8.1.7 *p等价num

8.1.8 指针变量的注意事项

1 、void 不能定义普通变量

2 、void * 可以定义指针变量

p就是万能的一级指针变量, 能保存 任意一级指针的地址编号

万能指针 一般用于 函数的形参 达到算法操作多种数据类型的目的。

记住:不要直接对void *p 的指针变量 取 *

对p取*之前 对p先进行指针类型强转。

short *p = (short *)&num;
*(p+ 1 );
char *p = (char *)&num;
*(p+ 2 );
char *p = (char *)&num;
*(short *)(p+ 1 )
int num = 10 ;
int *p = &num;
//p==&num
//*p == *&num == num
void num;//error 不能给num开辟空间
void *p;//ok p自身类型为void *,在 32 为平台任意类型的指针 为4B
那么系统知道为p开辟4B空间,所以定义成功
int num = 10 ;
void *p = &num;
short data = 20 ;
p = &data;
int num = 10 ;
void *p = &num;
*p;//err p指向的类型为void 无法确定p的取值宽度 所以不能*p

3 、指针变量 未初始化 不要取*

4 、指针变量 初始化NULL 不要取*

5 、指针变量 不要越界访问

8.2 数组元素的指针

8.2.1数组元素的指针概述

数组元素的指针变量 是用来保存数组元素的地址

int num= 10 ;
void *p = &num;
cout<<*(int *)p<<endl;
int *p;
*p;//err 段错误
int *p = NULL;
*p;//err 段错误
char ch = 'a';
int *p = &ch;
*p;//error 越界访问非法内存
int num = 10int *p = &num;
p++;
*p;//越界访问
int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
//需求定义一个指针变量 保存 数组元素的地址
int *p;
p = &arr[ 0 ];
p = arr;//arr作为地址 第 0 个元素的地址 arr==&arr[0]
p = &arr[ 3 ];

8.2.2 数组元素的指针变量 和 数组名(作为地址) 等价

8.2.3 在使用中 【】就是 *()的缩写

为啥arr ==&arr[0]?

案例 1 :p[-1]的值__ 30 __

案例 2 :p[1]的值__ 50 __

int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
int n = sizeof(arr)/sizeof(arr[ 0 ]);
int *p = arr;//p=arr
//遍历整个数组元素
int i;
for(i= 0 ;i<n;i++)
{
//cout<<arr[i]<<" ";
//cout<<*(arr+i)<<" ";
cout<<*(p+i)<<" ";
}
cout<<endl;
int arr[ 5 ] = { 10 , 20 , 30 , 40 , 50 };
int n = sizeof(arr) / sizeof(arr[ 0 ]);
cout<<"arr[1] = "<< arr[ 1 ]<<endl;//20
cout<<"*(arr+1) = "<< *(arr + 1 )<<endl;;//20
cout<<"--------------------"<<endl;
cout<<"*(arr+1) = "<<*( 1 + arr)<<endl;//20
cout<<"1[arr] = "<< 1 [arr]<<endl;//20
//[]是*()的缩写 []左边的值 放在+的左边 []里面的值 放在+右边 整体取*
&arr[ 0 ] == &*(arr+ 0 ) == arr+ 0 == arr
int arr[ 5 ] = { 10 , 20 , 30 , 40 , 50 };
int *p = arr+ 3 ;

8.2.4 指向同一数组的元素的两个指针变量间的关系

8.3 字符串与指针

8.3.1 字符数组

str1是数组 开辟 128 字节 存放字符串"hello world"

8.3.2 字符串指针变量

int arr[ 5 ] = { 10 , 20 , 30 , 40 , 50 };
int *p = arr+ 3 ;
char str1[ 128 ]="hello world";
sizeof(str1) == 128 字节
char *str2="hello world";
sizeof(str2) == 4 字节 或 8 字节

str2是指针变量 在栈区/全局区 保存的是字符串常量"hello world"的首元素地址,而”hello world“在文字

常量区

8.4 指针数组

指针数组:本质是数组 只是数组的每个元素为 指针

32 位平台:

char *arr1[ 4 ];
short *arr2[ 4 ];
int *arr3[ 4 ];
sizeof(arr1) ==16B
sizeof(arr2) ==16B
sizeof(arr3)  ==16B

8.4.1 数值的指针数组

int num1 = 10 ;
int num2 = 20 ;
int num3 = 30 ;
int num4 = 40 ;
int *arr[ 4 ] = {&num1, &num2, &num3, &num4};
int n = sizeof(arr)/sizeof(arr[ 0 ]);
int i;
for(i= 0 ;i<n;i++)
{
cout<<*arr[i]<<" ";//10 20 30 40
}
cout<<endl;

8.4.2 字符指针数组

char *arr[ 4 ]={"hehehehe", "xixixixixi", "lalalalala", "hahahahaha"};
char *arr[ 4 ]={"hehehehe","xixixixi", "lalalala", "hahahahaha"};
int n = sizeof(arr)/sizeof(arr[ 0 ]);
int i;
for(i= 0 ;i<n;i++)
{
cout<<arr[i]<<endl;
}

8.4.3 二维字符数组

arr1是在指针数组 存放的是每个字符串的首元素的地址

arr2是二维字符数组 存放的是每个字符串

char *arr1[ 4 ]={"hehehehe", "xixixixixi", "lalalalala", "hahahahaha"};
char arr2[ 4 ][ 128 ]={"hehehehe", "xixixixixi", "lalalalala", "hahahahaha"};

8.5 指针的指针

n级指针变量 可以保存 n-1级指针变量的地址

int num = 10 ;
int *p = &num;
int **q = &p;

8.6 数组指针

8.6.1 数组首元素地址 和 数组首地址

数组首元素地址:&arr[0] == arr arr+1跳过一个元素

数组的首地址:&arr &arr+1跳过整个数组

8.6.2 数组指针 本质是指针变量 保存的是数组的首地址

int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
int (*p)[ 5 ] = NULL;//数组指针
int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
int (*p)[ 5 ] = &arr;//数组指针

8.6.3 数组指针的案例

int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
int (*p)[ 5 ] = &arr;//数组指针

cout<< *((int *)(p+ 1 )- 2 ) <<endl;//40 分析为啥是 40

总结:

int *arr[ 5 ];//指针数组 本质是数组 每个元素为int *
int (*arr)[ 5 ];//数组指针 本质是指针变量 保存的是数组的首地址(概数组必须 5 个元素每个元素为
int

8.6.4 二维数组和数组指针的关系

1 、深入了解二维数组

arr[ 1 ]  => *(arr+ 1 )  第一行第 0 列的列地址
&arr[ 1 ] => &*(arr+ 1=>arr+ 11 行的行地址
*arr+ 1 =>0 行第 1 列的列地址
arr[ 1 ]+ 2 =>*(arr+ 1 )+ 2 =>1 行第 2 列的列地址
**arr ==*(*(arr+ 0 )+ 0 ) == arr[ 0 ][ 0 ]

2 、二维数组和一维数组指针的关系

int arr[n]; int *p;
int arr[n][m]; int (*p)[m];
int arr[n][m][k]; int (*p)[m][k]
n维数组 和n- 1 维的数组指针 等价

8.7 多维数组的物理存储

不管几维数组在物理上 都是一维存储,在逻辑上是多维的。

int arr[ 3 ][ 4 ]={{ 1 , 2 , 3 , 4 }, { 5 , 6 , 7 , 8 },{ 9 , 10 , 11 , 12 }};
int row = sizeof(arr)/sizeof(arr[ 0 ]);
int col = sizeof(arr[ 0 ])/sizeof(arr[ 0 ][ 0 ]);
int *p = &arr[ 0 ][ 0 ];
int i= 0 ;
for(i= 0 ;i<row*col;i++)
{
cout<<p[i]<<" ";
}
cout<<endl;

8.8 指针与函数

8.8.1 指针变量作为函数的参数

如果想在函数内部 修改外部变量的值 需要将外部变量的地址 传递给函数。(重要)

案例 1 :单向传递 之 传值

函数内部 不能修改外部变量的值

void setNum01(int data)
{
data = 100 ;
}
void test01()
{
int num = 0 ;
setNum01(num);//单向传递之 传值
cout<<"num = "<<num<<endl;//0 修改不成功
}

案例 2 :单向传递 之 传地址

函数内部 就可以修改 外部变量的值

void setNum02(int *p)//int *p=&num;
{
//*p == num
*p = 100 ;
}
void test01()
{
int num = 0 ;
setNum02(&num);//单向传递之 传地址
cout<<"num = "<<num<<endl;//100 修改成功
}

8.8.2 一维数组作为函数的参数

函数内部 想操作(读或写)外部数组元素,将数组名 传递给函数。

一维数组 作为函数的形参 会被优化成 指针变量。

//void ouputIntArray(int arr[5], int n)
//一维数组作为函数的参数 会被编译器 优化成 指针变量
void ouputIntArray(int *arr, int n)
{
cout<<"内部sizeof(arr) = "<<sizeof(arr)<<endl;//4B
int i= 0 ;
for(i= 0 ;i<n;i++)
{
//cout<<*(arr+i)<<" ";
cout<<arr[i]<<" ";//推荐
}
cout<<endl;
}
void test02()
{
int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
int n = sizeof(arr)/sizeof(arr[ 0 ]);
cout<<"外部sizeof(arr) = "<<sizeof(arr)<<endl;//20B
//遍历数组
ouputIntArray(arr, n);// 10 20 30 40 50
}

8.8.3 二维数组作为函数的参数

函数内部 想操作 函数外部的二维数组 需要将二维数组名 传递给函数

二维数组 作为函数的形参 会被优化成 一维的数组指针。

//void outputIntDoubleArray(int arr[3][4], int row, int col)
//二维数组 作为函数的形参 会被优化成 一维的数组指针
void outputIntDoubleArray(int (*arr)[ 4 ], int row, int col)
{
cout<<"内部sizeof(arr) = "<<sizeof(arr)<<endl;//4B
int i= 0 ,j= 0 ;
for(i= 0 ;i<row;i++)
{
for(j= 0 ;j<col;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}
}
void test03()
{
int arr[ 3 ][ 4 ]={ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 };
int row = sizeof(arr)/sizeof(arr[ 0 ]);
int col = sizeof(arr[ 0 ])/sizeof(arr[ 0 ][ 0 ]);
cout<<"外部sizeof(arr) = "<<sizeof(arr)<<endl;//48B
outputIntDoubleArray(arr, row, col);
}

8.8.4 函数的返回值类型 为 指针类型

将函数内部的合法地址 通过返回值 返回给函数外部使用。

注意:函數不要返回 普通局部变量的地址

int* getAddr(void)
{
//int data = 100;//不要返回普通局部变量的地址
static int data = 100 ;
return &data;
}
void test04()
{
int *p = NULL;
p = getAddr();
cout<<"*p = "<<*p<<endl;//100
}

8.9 函数指针

8.9.1 函数指针的定义

函数名 代表函数的入口地址;

函数指针:本质是一个指针变量 只是该变量 保存的是函数的入口地址

//函数指针 p只能保存 有两int形参以及int返回值 的函数入口地址
int (*p)(int, int) = NULL;
int myAdd(int x,int y)
{
return x+y;
}
void test05()
{
int (*p)(int x,int y) = NULL;
cout<<"sizeof(p) = "<<sizeof(p)<<endl;//4B

//函数指针 和 函数入口地址建立关系
p = myAdd;

//通过p调用myAdd函数(函数+(实参))
cout<<p( 10 , 20 )<<endl;//30
}

8.9.2 函数指针变量注意

函数指针变量 不要+1 无意义

不要对函数指针变量取* 无意义

int (*p)(int, int) = my_add;
*p会被编译器优化成p

函数指针变量 判断大小 > < 无意义

函数指针变量 可以赋值 p2=p1

函数指针变量 可以判断相等 p2 ==p1

8.9.3 函数指针变量 使用typedef定义

int myAdd(int x,int y)
{
return x+y;
}
void test05()
{
//给函数指针类型取别名
typedef int (*FUN_TYPE)(int x,int y);
FUN_TYPE p=NULL;
cout<<"sizeof(p) = "<<sizeof(p)<<endl;//4B
//函数指针 和 函数入口地址建立关系
p = myAdd;
//通过p调用myAdd函数(函数+(实参))
cout<<p( 10 , 20 )<<endl;//30
}

8.9.4 函数指针作为函数的参数

目的:让算法功能多样化

案例 1 :设计一个算法,完成加减乘除

int myAdd(int x,int y)
{
return x+y;
}
int mySub(int x,int y)
{
return x-y;
}
int myMul(int x,int y)

{

return x*y;
}
int myDiv(int x,int y)
{
return x/y;
}

//设计算法 操作上面的函数
int myCalc(int x,int y,  int (*func)(int,int) )
{
return func(x,y);
}
void test06()
{
cout<<myCalc( 10 , 20 , myAdd)<<endl;//30
cout<<myCalc( 10 , 20 , mySub)<<endl;//-10
cout<<myCalc( 10 , 20 , myMul)<<endl;//200
cout<<myCalc( 10 , 20 , myDiv)<<endl;//0
}

第九章 动态空间申请

9.1 动态分配内存的概述

在数组一章中,介绍过数组的长度是预先定义好的,在整个程序中固定不变,但是在实际的编程中,

往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定 。为了解决上述问

题,C++语言提供了一些关键字,可以按需要动态的分配内存空间,也可把不再使用的空间回收再次利

用。

9.2 静态分配、动态分配

9.2.1 静态分配

1 、在程序编译或运行过程中,按事先规定大小分配内存空间的分配方式。int a [10];

2 、必须事先知道所需空间的大小。

3 、分配在栈区或全局变量区,一般以数组的形式

9.2.2 动态分配

1 、在程序运行过程中,根据需要大小自由分配所需空间。

2 、按需分配。

3 、分配在堆区,一般使用特定的关键字进行分配

9.3 new和delete

9.3.1 new和delete操作基本类型空间

new申请堆区空间 delete释放空间

int *p = NULL;
p = new int;//从堆区申请int类型大小的空间
*p = 100 ;
cout<<"*p="<<*p<<endl;//100
//释放空间
delete p;
//new申请空间的同时 初始化空间内容
int *p1 = NULL;
p1 = new int( 100 );
cout<<"*p="<<*p<<endl;//100
delete p1;

9.3.2 new和delete操作数组空间

new与[]结合表示申请数组空间 delete释放的时候也需要[]

第十章 字符串处理函数

10.1 字符串操作函数

以str开头的字符串处理函数 默认遇到’\0’结束操作

#include <string.h>

10.1.1 测量字符串的长度 strlen

#include <string.h>
size_t strlen(const char *s);
s指 需要测量字符串的首元素地址
char str1[ 128 ]="hello";
strlen(str1);//5
char str2[ 128 ]="he\0llo";
strlen(str2);//2

10.1.2 字符串拷贝函数 strcpy

#include <string.h>
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
dest:目的空间地址
src:原字符串的首元素地址
char dst[ 128 ]="";
char src[]="hello\0world";
strcpy(dst,src);
cout<<dst<<endl;//hello
char dst1[]="";//dst1数组只有 1 字节 当src1拷贝到dst1中 造成溢出(内存污染)
char src1[]="helloworld";
strcpy(dst1, src1);
cout<<dst1<<endl;//hello world(污染内存)

10.1.3 字符串追加函数strcat

#include <string.h>
char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n);
将src指向的字符串 追加到 dest指向的字符串尾部
char dst[ 128 ]="hello";
char src[]="world";
strcat(dst,src);
cout<<dst<<endl;//helloworld

10.1.4 字符串比较strcmp

include <string.h>
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);

返回值:
> 0 s1字符串 > s2字符串
< 0 s1字符串 < s2字符串
== 0 s1字符串==s2字符串

char str1[ 128 ]="";

char str2[ 128 ]="";

cout<<"请输入第一个字符串:";
cin>>str1;
cout<<"请输入第二个字符串:";
cin>>str2;

if(strcmp(str1, str2) >  0 )
{
cout<<str1<<"大于"<<str2<<endl;
}
else if(strcmp(str1, str2) <  0 )
{
cout<<str1<<"小于"<<str2<<endl;
}
else if(strcmp(str1, str2) ==  0 )
{
cout<<str1<<"等于"<<str2<<endl;
}

第十一章 结构体

11.1 结构体的概述

有时我们需要将不同类型的数据组合成一个有机的整体,如:

一个学生有学号/姓名/性别/年龄/地址等属性:

int num;
char name[ 20 ]char sex;
int age;
char addr[ 30 ];

显然单独定义以上变量比较繁琐,数据不便于管理.

c++提供struct关键字 可以将不同类型封装在一起,形成新的结构叫做"结构体"

struct Student
{
int num;
char name[ 20 ]char sex;
int age;
char addr[ 30 ];
};

11.1.1 结构体类型的定义

struct 结构体类型名
{
成员列表;
};//注意此处有分号

如:

//定义结构体类型

//系统不会为结构体类型开辟空间 只会为结构体类型定义的变量开辟空间
struct Student
{
//int num=10;//定义结构体类型时 不要给成员初始化值
int num;//结构体成员
char name[ 32 ];
};
//结构体中的成员拥有独立的空间
//结构体定义变量
Student lucy;//lucy为结构体变量名
Student bob;//bob为结构体变量名

访问结构体变量中成员的方法:结构体变量.成员名

三种定义结构体类型的方法:

11.2 结构体变量的操作

lucy是一个局部的结构体变量 不初始化 变量中的成员内容不确定(随机值)

11.2.1 结构体变量的初始化

结构体变量的初始化 必须遵循成员的顺序以及成员自身的数据类型

struct Student
{
int num;//结构体成员
char name[ 32 ];
};
Student lucy={ 100 ,"lucy"};
cout<<lucy.num<<" "<<lucy.name<<endl;//100 lucy

11.2.2 清空整个结构体变量

使用memset清空结构体变量。

void *memset(void *_Dst,int _Val,size_t _Size);
将地址从_Dst开始 长度位_Size的所有字节赋值位_Val
#include<string.h>
struct Stu
{
int num;
char name[ 32 ];
};

void test02()
{
Stu lucy;
//清空整个结构体变量
memset(&lucy, 0 , sizeof(lucy));
cout<<lucy.num<<" "<<lucy.name<<endl;
}

11.2.3 键盘给结构体变量中成员赋值

#include<string.h>
struct Stu
{
int num;
char name[ 32 ];
};
void test03()
{
Stu lucy;
memset(&lucy, 0 ,sizeof(Stu));
cout<<"请输入学号 姓名:";
cin>>lucy.num>>lucy.name;
cout<<lucy.num<<" "<<lucy.name<<endl;
}

11.2.4 可以单独操作结构体中成员

单独操作结构体中成员 必须遵循结构体自身的类型

#include<string.h>
struct Stu
{
int num;
char name[ 32 ];
};
void test04()
{
Stu lucy ={ 100 ,"lucy"};
lucy.num += 100 ;
//name成员时数组名 位符号常量 不允许用=给name赋值
//lucy.name = "bob";//err
strcpy(lucy.name, "bob");
cout<<lucy.num<<" "<<lucy.name<<endl;//200 bob
}

11.2.5 相同类型的结构体变量 之间赋值方法

Stu lucy = { 100 ,"lucy"};
Stu bob;
#if 0
//第一种方法:逐个成员赋值(遵循成员类型)
bob.num = lucy.num;
strcpy(bob.name, lucy.name);
#elif 1
//第二中方法:相同类型的结构体变量 可以直接赋值(推荐)
bob = lucy;
#else
//第三种方式:内存拷贝(是第二种的底层实现)
memcpy(&bob, &lucy, sizeof(Stu));
#endif
cout<<bob.num<<" "<<bob.name<<endl;//100 lucy

11.3 结构体嵌套结构体

结构体嵌套结构体 注意访问到最底层

struct Date
{
int year;
int month;
int day;
};
struct Student
{
int num;
char name[ 32 ];
Date ob;
};
Student lucy;
cout<<lucy.num<<" "<<lucy.name<<endl;
cout<<lucy.ob.year<<" "<<lucy.ob.month<<" "<<lucy.ob.day<<endl;

11.4 结构体数组

结构体数组:本质是数组 只是数组的每个元素位结构体变量

11.4.1 结构体数组的定义

11.4.2 键盘给结构体数组赋值

11.5 结构体指针变量

结构体的指针变量:本质是变量 只是该变量 保存的是结构体变量的地址

struct Stu
{
int num;
char name[ 32 ];
};
Stu lucy={ 100 ,"lucy"};

11.5.1 结构体指针变量的定义

11.5.2 结构体数组元素的指针变量

指针变量 保存结构体数组 元素的地址

#include<string.h>
struct Stu
{
int num;
char name[ 32 ];
};
void inputStuArray(Stu *arr, int n)
{
cout<<"请输入"<<n<<"个学生信息(num,name)"<<endl;
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cin>>(arr+i)->num>>(arr+i)->name;
}
}
void sortStuArray(Stu *arr, int n)
{
int i= 0 ;
for(i= 0 ;i<n- 1 ;i++)
{
int j= 0 ;
for(j= 0 ;j<n-i- 1 ;j++)
{
if(  arr[j].num > arr[j+ 1 ].num )
{
Stu tmp = arr[j];
arr[j] = arr[j+ 1 ];
arr[j+ 1 ] = tmp;
}
}
}
}
void outputStuArray(Stu *arr, int n)
{

int i= 0 ;
for(i= 0 ;i<n;i++)
{
cout<<arr[i].num<<" "<<arr[i].name<<endl;
}
}
void test09()
{
Stu arr[ 5 ];
memset(arr, 0 ,sizeof(arr));
int n = sizeof(arr)/sizeof(arr[ 0 ]);

//封装函数获取键盘输入
inputStuArray(arr, n);

//对结构体数组按学号排序
sortStuArray(arr, n);

//输出结构体数组元素的内容
outputStuArray(arr, n);
}

11.6 结构体的指针成员

11.6.1 结构体的指针成员定义

struct Stu
{
int num;
char *name;
};
Stu lucy = { 100 , "hello world"};

lucy.name保存的时"hello world"的首元素地址, 而"hello world"字符串本身存储在文字常量区

11.6.2 结构体指针成员指向 堆区

11.6.3 结构体的浅拷贝

相同类型的结构体变量可以整体赋值,默认赋值方式为:浅拷贝

浅拷贝:将结构体变量空间内容 赋值一份 到另一个相同类型的结构体变量空间中

如果结构体中没有指针成员 浅拷贝 不会带来问题。

如果结构体中有指针成员 浅拷贝 会带来多次释放堆区空间的问题。

struct Stu
{
int num;
char *name;
};
Stu lucy;
lucy.num = 100 ;
lucy.name = new char[ 32 ];
strcpy(lucy.name, "hello world");
cout<<lucy.num<<" "<<lucy.name<<endl;
delete [] lucy.name;
struct Stu
{
int num;
char *name;
};
Stu lucy;
lucy.num = 100 ;
lucy.name = new char[ 32 ];
strcpy(lucy.name, "hello world");

Stu bob;
bob = lucy;

cout<<bob.num<<" "<<bob.name<<endl;
delete [] lucy.name;
delete [] bob.name;

11.6.4 结构体的深拷贝

如果结构体中有指针成员 尽量使用深拷贝

所谓的深拷贝,就是为结构体的指针成员 分配独立空间 然后再内容拷贝

struct Stu
{
int num;
char *name;
};

Stu lucy;
lucy.num = 100 ;
lucy.name = new char[ 32 ];
strcpy(lucy.name, "hello world");

Stu bob;
bob.num = lucy.num;
bob.name = new char[ 32 ];
strcpy(bob.name,lucy.name);

cout<<bob.num<<" "<<bob.name<<endl;
delete [] lucy.name;
delete [] bob.name;

11.6.5 结构体变量在堆区 结构体的指针成员也指向堆区

struct Stu
{
int num;
char *name;
};
//结构体在堆区
Stu *p = new Stu;
//结构体中指针成员指向堆区
p->name = new char[ 32 ];
//赋值
p->num = 100 ;
strcpy(p->name, "hello world");
cout<<p->num<<" "<<p->name<<endl;
//释放空间
delete [] p->name;//先释放成员指向
delete p;//再释放结构体

11.7 结构体的对齐规则

11.7.1 知识点引入

17.7.2 结构体自动对齐规则

1 、确定分配单位(一行分配多少字节)

结构体中最大的基本类型 长度决定。

2 、确定成员的偏移量

成员偏移量 = 成员自身类型的整数倍。

3 、收尾工作

结构体的总大小 = 分配单位整数倍

案例 1 :画出下列结构体的内存布局

struct Data
{
char a;
short b;
int c;
char d;
short e;
};

17.7.3 强制对齐规则

#pragma pack (value)时的指定对齐值value。

注意value值为1 2 4 8 16

1 、确定分配单位(一行分配多少字节)

分配单位 = min(结构体中最大的基本类型, value)

2 、确定成员的偏移量

成员偏移量 = 成员自身类型的整数倍。

3 、收尾工作

结构体的总大小 = 分配单位整数倍

案例 1 :下列结构体的内存布局

案例 2 :下列结构体的内存布局

11.8 结构体的位域

11.8.1 结构体位域的概述

在结构体中,以位为单位的成员,咱们称之为位段(位域)

a的类型是unsigned int a的大小 只占 2 位二进制位。

没有非位域隔开的位域 叫相邻位域。

相同类型的相邻位域可以压缩。但是压缩的位数 不能超过自身类型的大小。

不要对位域 取地址

对位域赋值 不要操作 位域本身位的宽度

11.8.2 另起一个存储单元

11.8.3 无意义位段(重要)

11.8.4 案例

11.9 共用体union

结构体:所有成员拥有独立空间

共用体:所有成员共享同一块空间

共用体关键字:union

共用体的空间 是由最大的成员类型决定

union Data
{
char a;
short b;
int c;
};

案例 1 :

union Data
{
char a;
short b;

int c;
};
void test05()
{
Data ob;
ob.a = 10 ;
ob.b = 20 ;
ob.c = 30 ;
cout<<ob.a+ob.b+ob.c<<endl;//90==30+30+30
}

成员a b c共享同一块空间,但是每个成员 能操作的空间的范围 是由成员自身类型长度决定。

union Data
{
char a;
short b;
int c;
};
Data ob;
ob.c = 0x01020304;
ob.b = 0x0102;
ob.a = 0x01;
cout<<ob.a+ob.b+ob.c<<endl;//0x01020203

11.9 枚举 enum

枚举:将枚举变量 要赋的值 一一列举出来

enum POKER_COLOR{HONGTAO,MEIHUA,FANGKUAI,HEITAO};

如果修改某个枚举列表的值

第十二章 链表

12.1 链表的概述

12.1.1 数组和链表的优缺点

静态数组:int arr[5]; 必须事先确定数组元素的个数,过多浪费 过小容易溢出,删除插入数据效率低

(需要移动大量的数据)

动态数组:不需要事先知道元素的个数,在使用中动态申请,删除插入数据效率低(需要移动大量的数

据)

(数组优点:遍历元素效率高)

链表:不需要事先知道数据的个数,在使用中动态申请,插入删除不需要移动数据

(链表缺点:遍历效率低)

12.1.2 链表的概述

链表是由一个个节点组成,节点没有名字,每个节点从堆区动态申请,节点间物理上是非连续的,但是

每个节点通过指针域 保存下一个节点的位置 达到逻辑上连续。

12.2 静态链表

12.2.1 设计链表节点

12.3 学生管理系统

以学生管理系统案例,来完成动态链表的学习。

C++中级部分

第十三章 C++对c的扩展

13.1 面向对象编程概述

#include <iostream>
using namespace std;

struct stu
{
//数据域
int num;
char name[ 32 ];

//指针域
struct stu *next;
};
int main(int argc, char const *argv[])
{
struct stu node1 = { 100 , "lucy", NULL};
struct stu node2 = { 101 , "bob", NULL};
struct stu node3 = { 102 , "tom", NULL};
struct stu node4 = { 103 , "德玛", NULL};
struct stu node5 = { 104 , "小法", NULL};

//定义链表头
struct stu *head = &node1;
node1.next = &node2;
node2.next = &node3;
node3.next = &node4;
node4.next = &node5;
node5.next = NULL;

//遍历
struct stu *pb = head;
while (pb != NULL)
{
//访问数据
cout<<pb->num<<" "<<pb->name<<endl;

//pb移动到下一个节点位置
pb = pb->next;
}
return 0 ;
}

13.1.1 面向过程

面向过程是一种以过程为中心的编程思想。 通过分析出解决问题所需要的步骤,然后用函数把这些步骤

一步一步实现,使用的时候一个一个依次调用就可以了。 面向过程编程思想的核心:功能分解,自顶向

下,逐层细化(程序=数据结构+算法)。 面向过程编程语言存在的主要缺点是不符合人的思维习惯,

而是要用计算机的思维方式去处理问题,而且面向过程编程语言重用性低,维护困难。

13.1.2 面向对象

面向对象编程(Object-Oriented Programming)简称 OOP 技术,是开发计算机应用程序的一种新方

法、新思想。过去的面向过程编程常常会导致所有的代码都包含在几个模块中,使程序难以阅读和维

护。在做一些修改时常常牵一动百,使以后的开发和维护难以为继。而使用 OOP 技术,常常要使用许

多代码模块,每个模块都只提供特定的功能,它们是彼此独立的,这样就增大了代码重用的几率,更加

有利于软件的开发、维护和升级。

在面向对象中,算法与数据结构被看做是一个整体,称作对象,现实世界中任何类的对象都具有一定的

属性和操作,也总能用数据结构与算法两者合一地来描述,所以可以用下面的等式来定义对象和程序:

对象 = 算法 + 数据结构 程序 = 对象 + 对象 + …

从上面的等式可以看出,程序就是许多对象在计算机中相继表现自己,而对象则是一个个程序实体。 面

向对象编程思想的核心:应对变化,提高复用。

13.1.3 面向对象的三大特点

封装 把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可

信的进行信息隐藏。 类将成员变量和成员函数封装在类的内部,根据需要设置访问权限,通过成员函数

管理内部状态。

继承 继承所表达的是类之间相关的关系,这种关系使得对象可以继承另外一类对象的特征和能力。 继承

的作用:避免公用代码的重复开发,减少代码和数据冗余。

多态 多态性可以简单地概括为“一个接口,多种方法”,字面意思为多种形态。程序在运行时才决定调用

的函数,它是面向对象编程领域的核心概念

13.2 ::作用域运算符

::解决归属问题(谁是谁的谁)

13.3 命名空间 namepace

创建名字是程序设计过程中一项最基本的活动,当一个项目很大时,它会不可避免地包含大量名字。

c++允许我们对名字的产生和名字的可见性进行控制。 我们之前在学习c语言可以通过static关键字来使

得名字只得在本编译单元内可见,在c++中我们将通过一种通过命名空间来控制对名字的访问。

13.3.1 C++命名空间(namespace)

在c++中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称

互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程

序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入关键字

namespace(命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。

13.3.2 命名空间使用语法

1 、创建一个命名空间:

namespace A{
int a = 10 ;
}
namespace B{
int a = 20 ;
}
void test(){
cout << "A::a : " << A::a << endl;//10
cout << "B::a : " << B::a << endl;//20
}

2 、命名空间只能全局范围内定义(以下错误写法)

void test(){
namespace A{
int a = 10 ;
}
namespace B{
int a = 20 ;
}
cout << "A::a : " << A::a << endl;
cout << "B::a : " << B::a << endl;
}

3 、命名空间可嵌套命名空间

namespace A{
int a = 10 ;
namespace B{
int a = 20 ;
}
}
void test(){
cout << "A::a : " << A::a << endl;
cout << "A::B::a : " << A::B::a << endl;
}

4 、命名空间是开放的,即可以随时把新的成员加入已有的命名空间中

namespace A{
int a = 10 ;
}
namespace A{
void func(){
cout << "hello namespace!" << endl;
}
}
void test(){
cout << "A::a : " << A::a << endl;
A::func();
}

5 、声明和实现可分离

#pragma once
namespace MySpace{
void func1();
void func2(int param);
}
void MySpace::func1(){
cout << "MySpace::func1" << endl;
}
void MySpace::func2(int param){
cout << "MySpace::func2 : " << param << endl;
}

6 、无名命名空间

namespace{
int a = 10 ;
void func(){ cout << "hello namespace" << endl; }
}
void test(){
cout << "a : " << a << endl;
func();
}

意味着命名空间中的标识符只能在本文件内访问,相当于给这个标识符加上了static,使得其可以作为内

部连接

7 、命名空间别名

namespace veryLongName{
int a = 10 ;
void func(){ cout << "hello namespace" << endl; }
}
void test(){
namespace shortName = veryLongName;
cout << "veryLongName::a : " << shortName::a << endl;
veryLongName::func();
shortName::func();
}

13.3.3 using声明 命名空间中的成员 可用

namespace A{
int paramA = 20 ;
int paramB = 30 ;
void funcA(){ cout << "hello funcA" << endl; }
void funcB(){ cout << "hello funcA" << endl; }
}
void test(){
//1. 通过命名空间域运算符
cout << A::paramA << endl;
A::funcA();
//2. using声明成员可用
using A::paramA;
using A::funcA;
cout << paramA << endl;
//cout << paramB << endl; //不可直接访问
funcA();
//3. 同名冲突
//int paramA = 20; //相同作用域注意同名冲突
}

using声明成员碰到函数重载

namespace A{
void func(){}
void func(int x){}
int func(int x,int y){}
}
void test(){
using A::func;
func();
func( 10 );
func( 10 , 20 );
}

如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合。

13.3.4 using 声明整个命名空间可用

namespace A{
int paramA = 20 ;
int paramB = 30 ;
void funcA(){ cout << "hello funcA" << endl; }
void funcB(){ cout << "hello funcB" << endl; }
}
void test01(){
using namespace A;
cout << paramA << endl;
cout << paramB << endl;
funcA();
funcB();

//不会产生二义性

int paramA = 30 ;
cout << paramA << endl;
}
namespace B{
int paramA = 20 ;
int paramB = 30 ;
void funcA(){ cout << "hello funcA" << endl; }
void funcB(){ cout << "hello funcB" << endl; }
}
void test02(){
using namespace A;
using namespace B;
//二义性产生,不知道调用A还是B的paramA
//cout << paramA << endl;
}

注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,并在代

码中使用作用域解析运算符,则不会出现二义性。

13.4 struct类型增强

c中定义结构体变量需要加上struct关键字,c++不需要。 c中的结构体只能定义成员变量,不能定义成员

函数。c++即可以定义成员变量,也可以定义成员函数。

13.4.1 结构体中即可以定义成员变量,也可以定义成员函数

struct Student{
string mName;
int mAge;
void setName(string name){ mName = name; }
void setAge(int age){ mAge = age; }
void showStudent(){
cout << "Name:" << mName << " Age:" << mAge << endl;
}
};

13.4.2 c++中定义结构体变量不需要加struct关键字

void test01(){
Student student;
student.setName("John");
student.setAge( 20 );
student.showStudent();
}

13.5 bool类型关键字

标准c++的bool类型有两种内建的常量true(转换为整数1)和false(转换为整数0)表示状态。这三个名字都

是关键字。 bool类型只有两个值,true(1值),false(0值) bool类型占 1 个字节大小 给bool类型赋值时,

非 0 值会自动转换为true(1),0值会自动转换false(0)

void test()
{
cout << sizeof(false) << endl; //为 1 ,//bool类型占一个字节大小
bool flag = true;
flag = 10 ; //给bool类型赋值时,非 0 值会自动转换为true(1),0值会自动转换false(0)
}

13.6 引用(reference)

在c/c++中指针的作用基本都是一样的,但是c++增加了另外一种给函数传递地址的途径,这就是按引用

传递(pass-by-reference)。

变量名实质上是一段连续内存空间的别名,是一个标号(门牌号) 程序中通过变量来申请并命名内存空间

通过变量的名字可以使用存储空间

对一段连续的内存空间只能取一个别名吗? c++中新增了引用的概念,引用可以作为一个已定义变量的

别名。

13.6.1 引用的定义

引用的本质:就是给变量名取个 别名 。

引用定义的步骤:

1&别名

2 、给哪个变量取别名 就定义该变量

3 、从上往下整体替换

13.6.2 普通变量的引用

int a = 10 ;
//需求:给变量a 取个别名叫b
//定义的时候 &修饰变量为引用 b就是a的别名(引用)
//系统不会为引用 开辟空间
int &b = a;//引用必须初始化
//a和b代表同一空间内容
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"&a = "<<&a<<endl;
cout<<"&b = "<<&b<<endl;
//操作b等价操作a
b = 100 ;
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;

13.6.3 数组的引用

int arr[ 5 ]={ 10 , 20 , 30 , 40 , 50 };
int n = sizeof(arr)/sizeof(arr[ 0 ]);
int (&myArr)[ 5 ] = arr;
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cout<<myArr[i]<<" ";//10 20 30 40 50
}
cout<<endl;

13.6.4 指针变量的引用

int num = 10 ;
int *p = &num;
int* &myP = p;
cout<<"*p = "<<*p<<endl;//10
cout<<"*myP = "<<*myP<<endl;//10

13.6.5 函数的引用

void fun01(void)
{
cout<<"fun01"<,endl;
}
void (&myFun)(void) = fun01;
myFun();//fun01

13.6.6 引用作为函数的参数

函数内部可以 通过 引用 操作外部变量

void swap01(int *p1, int *p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}

void swap02(int &x, int &y)
{
int tmp = x;
x = y;
y = tmp;
}

int main()
{
int a = 10 ;
int b = 20 ;
cout<<"a = "<<a<<", b = "<<b<<endl;//a = 10, b = 20
//swap01(&a, &b);
swap02(a, b);

cout<<"a = "<<a<<", b = "<<b<<endl;//a = 20, b = 10
}

引用的语法更清楚简单:

13.6.7 引用作为函数的返回值类型

1 、不要返回普通局部变量的引用

int& getData(void)
{
int num = 10 ;

//不要返回局部变量的引用
return num;//返回num 函数调用的结果 就是num的别名
}
int main()
{
//b就是num的别名
int &b = getData();
}

2 、返回值类型为引用 可以完成链式操作

struct Stu
{
Stu& printStu(Stu &ob, int value)
{
cout<<value<<" ";
return ob;
}
};
int main()
{
Stu ob1;
ob1.printStu(ob1, 100 ).printStu(ob1, 200 ).printStu(ob1, 300 );//100 200 300
}

13.6.8 常引用

1 、给常量取别名,不能通过常引用 修改 内容。

//int &a = 10;//err
const int &a = 10 ;//a就是 10 的别名
//a=100;//err
cout<<a<<endl;//10

2 、常引用 作为函数的参数:防止函数内部修改外部的值。

void printInt(const int &a)
{
//a = 200;//err
cout<<"a = "<<a<<endl;
}
int main()
{
int num = 100 ;
printInt(num);//a = 100
}
  1. 函数调用时传递的实参不必加“&”符

  2. 在被调函数中不必在参数前加“*”符 引用作为其它变量的别名而存在,因此在一些场合可以代替指针。

  3. C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。

13.7 内联函数

13.7.1 声明内联函数

内联函数 必须在定义的时候 使用关键字inline修饰, 不能在声明的时候使用inline

//函數声明的时候 不要使用inline
int myAdd(int x, int y);
int main()
{
cout<<myAdd( 100 , 200 )<<endl;
}
//内联函数 在定义的时候使用inline
inline int myAdd(int x, int y)
{
return x+y;
}

内联函数:在 编译阶段 将内联函数中的 函数体 替换 函数调用处 。避免函数调用时的开销。

13.7.2 宏函数和内联函数的区别

宏函数和内联函数 都会在适当的位置 进行展开 避免函数调用开销。

宏函数在预处理阶段展开

内联函数在编译阶段展开

宏函数的参数没有类型,不能保证参数的完整性。

内联函数的参数有类型 能保证参数的完整性。

宏函数没有作用域的限制,不能作为命名空间、结构体、类的成员

内联函数有作用域的限制,能作为命名空间、结构体、类的成员

13.7.3 内联函数的注意事项

在内联函数定义的时候加inline修饰

类中的成员函数 默认都是内联函数(不加inline 也是内联函数)

有时候 就算加上inline也不一定是内联函数(内联函数条件)

不能存在任何形式的循环语句

不能存在过多的条件判断语句

函数体不能过于庞大

不能对函数取地址

有时候不加inline修饰 也有可能是内联函数。

内不内联 由编译器决定。

13.8 函数重载

13.8.1 函数重载的概述

能使名字方便使用,是任何程序设计语言的一个重要特征!

我们现实生活中经常会碰到一些字在不同的场景下具有不同的意思,比如汉语中的多音字“重”。 当我们

说: “他好重啊,我都背不动!”我们根据上下文意思,知道“重”在此时此地表示重量的意思。 如果我们说

“你怎么写了那么多重复的代码? 维护性太差了!”这个地方我们知道,“重”表示重复的意思。 同样一个字在

不同的场景下具有不同的含义。那么在c++中也有一种类似的现象出现,同一个函数名在不同场景下可

以具有不同的含义。

函数重载 是c++的多态的特性(静态多态)。

函数重载:用同一个函数名 代表不同的函数功能。

13.8.1 函数重载的条件

同一作用域,函数的参数类型不同、个数不同、顺序不同 都可以重载。(返回值类型不能作为重载的条

件)

void printFun(int a)
{
cout<<"int"<<endl;
}
void printFun(int a, char b)
{
cout<<"int char"<<endl;
}
void printFun(char a, int b)
{
cout<<"char int"<<endl;
}
void printFun(char a)
{
cout<<"char"<<endl;
}
int main()
{
printFun( 10 );//int
printFun( 10 , 'a');//int char
printFun('a', 10 );//char int
printFun('a');//char
}

思考:为什么函数返回值不作为重载条件呢?

当编译器能从上下文中确定唯一的函数的时,如int ret =func(),这个当然是没有问题的。然而,我们在编

写程序过程中可以忽略他的返回值。那么这个时候,一个函数为 void func(int x);另一个为int func(int x);

当我们直接调用func(10),这个时候编译器就不确定调用那个数。所以在c++中禁止使用返回值作为重载

的条件

13.8.2 函数重载的底层实现原理

以上三个函数在linux下生成的编译之后的函数名为

void func(){}
void func(int x){}
void func(int x,char y){}

不同的编译器可能会产生不同的内部名,只是举例说明

_Z4funcv //v 代表void,无参数
_Z4funci //i 代表参数为int类型
_Z4funcic //i 代表第一个参数为int类型,第二个参数为char类型

13.9 函数的默认参数

c++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指

定这个值,编译器会自动用默认值代替。

void TestFunc01(int a = 10 , int b = 20 ){
cout << "a + b = " << a + b << endl;
}
//注意点:
//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数
void TestFunc02(int a,int b = 10 ,int c = 10 ){}
//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0 ,int b = 0 );
void TestFunc03(int a, int b){
}
int main(){
//1.如果没有传参数,那么使用默认参数
TestFunc01();
//2. 如果传一个参数,那么第二个参数使用默认参数
TestFunc01( 100 );
//3. 如果传入两个参数,那么两个参数都使用我们传入的参数
TestFunc01( 100 , 200 );
return 0 ;
}

判断以下默认参数设置是否正确:

int func(int a, int b, int c= 10 );//正确
int func(int a, int b= 20 , int c);//错误 c必须默认参数
int func(int a= 10 , int b, int c);//错误 b c必须默认参数
int func(int a, int b= 10 , int c= 20 );//正确
int func(int a= 10 , int b, int c= 20 );//错误 b必须默认参数
int func(int a= 10 , int b= 20 , int c= 20 );//正确

默认参数和函数重载同时出现 一定要注意二义性

void func(int x)
{
cout<<"A:int x="<<x<<endl;
}
void func(int x,int y= 10 )
{
cout<<"B:int x="<<x<<" y="<<y<<endl;
}
int main()
{
func( 10 , 20 );//ok
func( 10 );//err 产生二义性
}

13.10 占位参数

c++在声明函数时,可以设置占位参数。占位参数只有参数类型,而没有参数名。一般情况下,在函数

体内部无法使用占位参数。

void TestFunc01(int a,int b,int){
//函数内部无法使用占位参数
cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值
void TestFunc02(int a, int b, int = 20 ){
//函数内部依旧无法使用占位参数
cout << "a + b = " << a + b << endl;
}
int main(){
//错误调用,占位参数也是参数,必须传参数
//TestFunc01(10,20);
//正确调用
TestFunc01( 10 , 20 , 30 );
//正确调用
TestFunc02( 10 , 20 );
//正确调用
TestFunc02( 10 , 20 , 30 );
return 0 ;
}

什么时候用,在后面我们要讲的操作符重载的后置++要用到这个.

13.11 extern “C” 浅析

以下在Linux下测试: c函数: void MyFunc(){} ,被编译成函数: MyFunc

c++函数: void MyFunc(){},被编译成函数: _Z6Myfuncv

通过这个测试,由于c++中需要支持函数重载,所以c和c++中对同一个函数经过编译后生成的函数名是

不相同的,这就导致了一个问题,如果在c++中调用一个使用c语言编写模块中的某个函数,那么c++是

根据c++的名称修饰方式来查找并链接这个函数,那么就会发生链接错误,以上例,c++中调用MyFunc

函数,在链接阶段会去找Z6Myfuncv,结果是没有找到的,因为这个MyFunc函数是c语言编写的,生成

的符号是MyFunc。 那么如果我想在c++调用c的函数怎么办? extern "C"的主要作用就是为了实现c++代

码能够调用其他c语言代码。加上extern "C"后,这部分代码编译器按c语言的方式进行编译和链接,而

不是按c++的方式。

fun.h

#ifndef MYMODULE_H
#define MYMODULE_H
#include<stdio.h>
#if __cplusplus
extern "C"{
#endif
extern void func1();
extern int func2(int a,int b);
#if __cplusplus
}
#endif
#endif

fun.c

#include<stdio.h>
#include"fun.h"
void func1(){
printf("hello world!");
}
int func2(int a, int b){
return a + b;
}

main.cpp

#include<iostream>
#include "fun.h"
using namespace std;
int main(){
func1();
cout << func2( 10 , 20 ) << endl;
return 0 ;
}

第十四章 类和对象

14.1 类和对象的基本概念

14.1.1 类的封装性

我们编写程序的目的是为了解决现实中的问题,而这些问题的构成都是由各种事物组成,我们在计算机

中要解决这种问题,首先要做就是要将这个问题的参与者:事和物抽象到计算机程序中,也就是用程序

语言表示现实的事物。 那么现在问题是如何用程序语言来表示现实事物?现实世界的事物所具有的共性

就是每个事物都具有自身的属性,一些自身具有的行为,所以如果我们能把事物的属性和行为表示出

来,那么就可以抽象出来这个事物。

类将具有共性的数据和方法封装在一起,加以权限区分,用户只能通过公共方法 访问私有数据。

类的权限分为:private(私有)、protected(保护)、public(公有) 3 种权限。

在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时, private和protected是同

等级的,外部不允许访问。用户在类的外部可以通过public的方法间接访问private和protected数据

14.1.2 定义一个类

类的关键字:class

#include <iostream>
using namespace std;
//类Data 是一个类型
class Data
{
//类中 默认为私有
private:
int a;//不要给类中成员 初始化
protected://保护
int b;
public://公共
int c;
//在类的内部 不存在 权限之分
void showData(void)
{
cout<<a<<" "<<b<<" "<<c<<endl;
}
};
int main()
{
//类实例化一个对象
Data ob;
//类外不能直接访问 类的私有和保护数据
//cout<<ob.a <<endl;//err
//cout<<ob.b <<endl;//err
cout<<ob.c <<endl;

//类中的成员函数 需要对象调用
ob.showData();
}

设计一个类步骤:思考该类有哪些数据成员 操作这些数据的成员函数 数据位私有 成员函数为公有

14.1.3 设计一个Person类

请设计一个Person类,Person类具有name和age属性,提供初始化函数(Init),并提供对name和age的

读写函数(set,get),但必须确保age的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值,并提供方

法输出姓名和年龄

#include <iostream>
#include <string.h>
using namespace std;
class Person
{
private:
char mName[ 32 ];
int mAge;
public:
//初始化函数
void personInit(char *name, int age)
{
strcpy(mName, name);
if(age> 0 && age<= 100 )
{
mAge = age;
}
else
{
cout<<"输入的年龄:"<<age<<"不合法"<<endl;
}
}

//设置mAge
void setAge(int age)
{
if(age> 0 && age<= 100 )
{
mAge = age;
}
else
{
cout<<"输入的年龄:"<<age<<"不合法"<<endl;
}
}
//得到mAge
int getAge(void)
{
return mAge;
}

//设置mName
void setName(char *name)
{
strcpy(mName, name);
}
//得到mName
char *getName(void)
{
return mName;
}
//显示年龄和姓名
void showPerson()
{
cout<<"姓名:"<<mName<<", 年龄:"<<mAge<<endl;
}
};
int main(int argc, char *argv[])
{
Person lucy;
lucy.personInit("lucy", 18 );

lucy.setName("tom");
lucy.setAge( 16 );

cout<<lucy.getName()<<" "<<lucy.getAge()<<endl;
lucy.showPerson();

return 0 ;
}

14.1.4 设计一个立方体类

设计立方体类(Cube),求出立方体的面积( 2ab + 2ac + 2bc )和体积( a * b * c),分别用全局函数和成员

函数判断两个立方体是否相等。

#include <iostream>
using namespace std;
class Cube
{
private:
int mL;
int mW;
int mH;
public:
void cubeInit(int l,int w,int h)
{
mL = l;
mW = w;
mH = h;
}
int getL(void)
{
return mL;

}

int getW(void)
{
return mW;
}
int getH(void)
{
return mH;
}

int getS(void)
{
return (mL*mW+mL*mH+mW*mH)* 2 ;
}

int getV(void)
{
return mL*mW*mH;
}

//成员函数实现比较
bool cubeCompare02(Cube &ob)
{
if(mL==ob.mL && mH==ob.mH && mW==ob.mW)
{
return true;
}
return false;
}
};

//全局函数实现 立方体的比较
bool cubeCompare01(Cube &ob1, Cube &ob2)
{
if(ob1.getH() == ob2.getH() &&
ob1.getL()==ob2.getL()&&ob1.getW()==ob2.getW())
{
return true;
}
return false;
}
int main(int argc, char *argv[])
{

Cube ob1;
ob1.cubeInit( 10 , 20 , 30 );
cout<<"面积:"<<ob1.getS()<<endl;
cout<<"体积:"<<ob1.getV()<<endl;

Cube ob2;
ob2.cubeInit( 10 , 20 , 30 );

//全局函数实现比较
if(cubeCompare01(ob1, ob2))
{
cout<<"相等"<<endl;
}
else
{
cout<<"不相等"<<endl;
}

//成员函数实现比较
if(ob1.cubeCompare02(ob2))
{
cout<<"相等"<<endl;
}
else
{
cout<<"不相等"<<endl;
}

return 0 ;
}

14.1.5 成员函数在类外实现

成员函数 在类中声明 类外实现

14.1.6 类在其他源文件中实现

通过Qt Creator为c++工程添加一个类(步骤如下)

class Data
{
private:
int mA;
public:
//类中声明
void setA(int a);
int getA(void);
};
//类外实现
void Data::setA(int a)
{
mA = a;
}

int Data::getA()
{
return mA;
}

注意:类定义在同文件data.h中,而data.cpp是用来实现类的成员函数

data.h

#ifndef DATA_H
#define DATA_H
class Data
{
private:
int mA;
public:
void setA(int a);
int getA(void);
};
#endif // DATA_H
data.cpp
```cpp
#include "data.h"
void Data::setA(int a)
{
mA=a;
}
int Data::getA()
{
return mA;
}

main.cpp
```cpp
#include <iostream>
#include "data.h"
using namespace std;

int main(int argc, char *argv[])
{
Data ob;
ob.setA( 100 );
cout<<"mA = "<<ob.getA()<<endl;//mA = 100
return 0 ;
}

14.2 构造函数

14.2.1 初始化和清理

当我们创建对象的时候,这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。

对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未

知,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。c++为了给我们提供这种问题

的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工

作。 无论你是否喜欢,对象的初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操

作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类

就应该顺便提供初始化函数。

为什么初始化操作是自动调用而不是手动调用?

既然是必须操作,那么自动调用会更好,如果靠程序员自觉,那么就会存在遗漏初始化的情况出现。

14.2.2 构造函数的概述

类实例化对象的时候 系统自动调用构造函数 完成对象的初始化。

如果用户不提供构造函数 编译器 会自动添加一个默认的构造函数(空函数)

14.2.3 构造函数的定义方式

构造函数名 和 类名相同,没有返回值类型(连void都不可以),可以有参数(可以重载),权限为

public

先给对象开辟空间(实例化) 然后调用构造函数(初始化)。

class Data
{
public:
int mA;
public:
//无参构造函数
Data()
{
mA= 0 ;
cout<<"无参构造函数"<<endl;
}
//有参构造函数
Data(int a)
{
mA=a;
cout<<"有参构造函数 mA="<<mA<<endl;
}
};
int main()
{
//隐式调用无参构造函数(推荐)
Data ob1;
//显示调用无参构造函数
Data ob2 = Data();

//隐式调用有参构造函数(推荐)
Data ob3( 10 );

//显示调用有参构造函数
Data ob4 = Data( 10 );

//匿名对象(无参) 当前语句技术 立即释放
Data();
Data( 20 );

//构造函数隐式转换(类中只有一个数据成员)
Data ob5 = 100 ;
}

14.2.4 提供构造函数的影响

如果用户不提供任何构造函数 编译器默认提供一个空的无参构造。

如果用户定义了构造函数(不管是有参、无参),编译器不再提供默认构造函数。

14.3 析构函数

14.3.1 析构函数的定义方式

函数名和类名称相同,在函数名前加~,没有返回值类型,没有函数形参。(不能被重载)

当对象生命周期结束的时候 系统自动调用析构函数。

先调用析构函数 再释放对象的空间。

class Data1
{
public:
int mA;
public:
//无参构造函数
Data1()
{
mA= 0 ;
cout<<"无参构造函数"<<endl;
}
//有参构造函数
Data1(int a)
{
mA=a;
cout<<"有参构造函数 mA="<<mA<<endl;
}

//析构函数
~Data1()
{
cout<<"析构函数 mA="<<mA<<endl;

}

};

一般情况下,系统默认的析构函数就足够。但是如果一个类有 指针成员 ,这个类必须 写析构函数,释放

指针成员所指向空间。

#include<string.h>
class Data2
{
public:
char *name;
public:
Data2(){
name=NULL;
}
Data2(char *str)
{
name = new char[strlen(str)+ 1 ];
strcpy(name, str);
cout<<"有参构造"<<endl;
}
~Data2()
{
if(name != NULL)
delete [] name;
cout<<"析构函数"<<endl;
}
};
int main(int argc, char *argv[])
{
Data2 ob("hello world");
cout<<ob.name<<endl;
return 0 ;
}

14.4 拷贝构造函数

14.4.1 拷贝构造函数的定义

拷贝构造:本质是构造函数

拷贝构造的调用时机:旧对象 初始化 新对象 才会调用拷贝构造。

#include <iostream>
using namespace std;
class Data
{
public:
int mA;
public:
Data()
{
cout<<"无参构造"<<endl;
}
Data(int a)
{
mA = a;
cout<<"有参构造 mA="<<mA<<endl;
}
#if 1
//拷贝构造的定义形式:ob就是旧对象的引用
Data(const Data &ob)
{
//一旦实现了 拷贝构造函数 必须完成赋值操作
mA = ob.mA;
cout<<"拷贝构造函数"<<endl;
}
#endif
~Data()
{
cout<<"析构函数mA="<<mA<<endl;
}
};
int main(int argc, char *argv[])
{
Data ob1( 10 );
//旧对象给新对象初始化 就会调用拷贝构造函数
Data ob2 = ob1;
cout<<"ob2.mA ="<<ob2.mA<<endl;
return 0 ;
}

如果用户不提供拷贝构造 编译器会自动提供一个默认的拷贝构造(完成赋值动作–浅拷贝)

14.4.2 拷贝构造 和 无参构造 有参构造的关系

如果用户定义了 拷贝构造或者有参构造 都会屏蔽无参构造。

如果用户定义了 无参构造或者有参构造 不会屏蔽拷贝构造。

14.4.3 拷贝构造几种调用形式

1 、旧对象给新对象初始化 调用拷贝构造

Data ob1( 10 );
Data ob2 = ob1;//调用拷贝构造

2 、给对象取别名 不会调用拷贝构造

Data ob1( 10 );
Data &ob2 = ob1;//不会调用拷贝构造

3 、普通对象作为函数参数 调用函数时 会发生拷贝构造

void func(Data ob)//Data ob=ob1
{

4 、函数返回值普通对象

}
int main()
{
Data ob1( 100 );//有参构造
func(ob1);//拷贝构造
}

Visual Studio会发生拷贝构造
Qtcreater,linux不会发生

14.4.4 拷贝构造的浅拷贝和深拷贝

默认的拷贝构造 都是浅拷贝。

如果类中没有指针成员, 不用实现拷贝构造和析构函数。

如果类中有指针成员,且指向堆区空间, 必须实现析构函数释放指针成员指向的堆区空间,必须实现拷

贝构造完成深拷贝动作。

#include<iostream>
#include<string.h>
using namespace std;
class Data5
{
public:
char* name;
public:
Data5()
{
name = NULL;
}
Data5(char* str)
{
name = new char[strlen(str) + 1 ];
strcpy(name, str);
cout << "有参构造 name=" << name << endl;
}
Data5(const Data5& ob)//深拷贝
{
//为对象的指针成员申请独立的空间
name = new char[strlen(ob.name) + 1 ];
strcpy(name, ob.name);
cout << "拷贝构造函数" << endl;
}
~Data5()
{
cout << "析构函数name = " << name << endl;
if (name != NULL)
{
delete [] name;
name = NULL;
}
}
};
void test05()
{
Data5 ob1((char *)"hello world\n");
Data5 ob2 = ob1;
}

14.5 初始化列表

14.5.1 对象成员

在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。

先调用对象成员的构造函数,再调用本身的构造函数。 析构函数和构造函数调用顺序相反,先构造,后

析构

类会自动调用对象成员的 无参构造 。

14.5.2 初始化列表

类想调用对象成员 有参构造 必须使用 初始化列表 。

class A
{
public:
int mA;
public:
A()
{
mA = 0 ;
cout<<"A的无参构造"<<endl;
}
A(int a)
{
mA = a;
cout<<"A的有参构造"<<endl;
}
~A()
{
cout<<"A的析构函数"<<endl;
}
};
class B
{
public:
int mB;
A ob;//成员对象
public:
B()

{
cout<<"B类的无参构造"<<endl;
}
//初始化列表 成员对象 必须使用对象名+() 重要
B(int a, int b): (a)
{
mB = b;
cout<<"B类的有参构造"<<endl;
}
~B()
{
cout<<"B的析构函数"<<endl;
}
};
int main(int argc, char *argv[])
{
B ob1( 10 , 20 );
cout<<"mA ="<<ob1.ob.mA<<", mB ="<<ob1.mB<<endl;
return 0 ;
}

14.6 explicit关键字

c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转

换中使用。

注意explicit用于修饰构造函数,防止隐式转化。 是针对单参数的构造函数(或者除了第一个参数外其余参

数都有默认值的多参构造)而言。

class MyString{
public:
explicit MyString(int n){
cout << "MyString(int n)!" << endl;
}
MyString(const char* str){
cout << "MyString(const char* str)" << endl;
}
};
int main(){
//给字符串赋值?还是初始化?
//MyString str1 = 1;
MyString str2( 10 );
//寓意非常明确,给字符串赋值
MyString str3 = "abcd";
MyString str4("abcd");
return 0 ;
}

14.7 类的对象数组

对象数组:本质是数组 数组的每个元素是对象。

class A
{
public:
int mA;
public:
A()
{
mA = 0 ;
cout<<"A的无参构造 mA="<<mA<<endl;
}
A(int a)
{
mA = a;
cout<<"A的有参构造mA="<<mA<<endl;
}
~A()
{
cout<<"A的析构函数 mA = "<<mA<<endl;
}
};
int main()
{
//对象数组 每个元素都会自动调用构造和析构函数
//对象数组不初始化 每个元素 调用无参构造
A arr1[ 5 ];

//对象数组的初始化 必须显示使用有参构造 逐个元素初始化
A arr2[ 5 ]={A( 10 ),A( 20 ),A( 30 ),A( 40 ),A( 50 ) };
int n =sizeof(arr2)/sizeof(arr2[ 0 ]);
int i= 0 ;
for(i= 0 ;i<n;i++)
{
cout<<arr2[i].mA<<" ";
}
cout<<endl;
}

14.8 动态对象创建

14.8.1 动态创建的概述

当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用

数组的时,会有这样的问题,数组也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如

果能根据需要来分配空间大小再好不过。 所以动态的意思意味着不确定性。 为了解决这个普遍的编程问

题,在运行中可以创建和销毁对象是最基本的要求。当然c早就提供了动态内存分配(dynamic

memory allocation),函数malloc和free可以在运行时从堆中分配存储单元。 然而这些函数在c++中不

能很好的运行,因为它不能帮我们完成对象的初始化工作。

14.8.2 c的方式创建动态对象

当创建一个c++对象时会发生两件事:

  1. 为对象分配内存

  2. 调用构造函数来初始化那块内存 第一步我们能保证实现,需要我们确保第二步一定能发生。c++强

迫我们这么做是因为使用未初始化的对象是程序出错的一个重要原因。 C动态分配内存方法为了在

运行时动态分配内存,c在他的标准库中提供了一些函数,malloc以及它的变种calloc和realloc,释放

内存的free,这些函数是有效的、但是原始的,需要程序员理解和小心使用。为了使用c的动态内存

分配函数在堆上创建一个类的实例,我们必须这样做:

class Person{
public:
Person(){
mAge = 20 ;
pName = (char*)malloc(strlen("john")+ 1 );
strcpy(pName, "john");
}
void Init(){
mAge = 20 ;
pName = (char*)malloc(strlen("john")+ 1 );
strcpy(pName, "john");
}
void Clean(){
if (pName != NULL){
free(pName);
}
}
public:
int mAge;
char* pName;
};
int main(){
//分配内存
Person* person = (Person*)malloc(sizeof(Person));
if(person == NULL){
return 0 ;
}
//调用初始化函数
person->Init();
//清理对象
person->Clean();
//释放person对象
free(person);
return 0 ;
}

问题:

  1. 程序员必须确定对象的长度。
2) malloc返回一个void指针,c++不允许将void赋值给其他任何指针,必须强转。
3) malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。
4) 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用
户有可能忘记调用初始化函数。

c的动态内存分配函数太复杂,容易令人混淆,是不可接受的,c++中我们推荐使用运算符new 和

delete。

14.8.3 new创建动态对象

C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里。当

用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。

Person* person = new Person;

New操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。现

在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类

型转换和安全检查。这样在堆创建一个对象和在栈里创建对象一样简单

14.8.4 delete释放动态对象

new表达式的反面是delete表达式。delete表达式先调用析构函数,然后释放内存。

class Person{
public:
Person(){
cout << "无参构造函数!" << endl;
pName = new char[strlen("undefined") + 1 ];
strcpy(pName, "undefined");
mAge = 0 ;
}
Person(char* name, int age){
cout << "有参构造函数!" << endl;
pName = new char[strlen(name) + 1 ];
strcpy(pName, name);
mAge = age;
}
void ShowPerson(){
cout << "Name:" << pName << " Age:" << mAge << endl;
}
~Person(){
cout << "析构函数!" << endl;
if (pName != NULL){
delete [] pName;
pName = NULL;
}
}
public:
char* pName;
int mAge;
};
int main(){
Person* person1 = new Person;
Person* person2 = new Person("John", 33 );
person1->ShowPerson();
person2->ShowPerson();
delete person1;
delete person2;
}

14.8.5 动态对象数组

当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上可以聚合初始化,

必须提供一个默认的构造函数

class Person{
public:
Person(){
pName = NULL;
mAge = 0 ;
}
Person(char* name, int age){
pName = new char[strlen(name)+ 1 ];
strcpy(pName, name);
mAge = age;
}
~Person(){
if (pName != NULL){
delete [] pName;
}
}
public:
char* pName;
int mAge;
};
void test(){
//栈聚合初始化
Person person[] = { Person("john", 20 ), Person("Smith", 22 ) };
cout << person[ 1 ].pName << endl;
//创建堆上对象数组必须提供构造函数
Person* workers = new Person[ 20 ];
delete [] workers;
}

14.9 静态成员

在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字static声明为静态的,称为

静态成员。 不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象

共享。

14.9.1 静态成员变量

static修饰的静态成员 属于类而不是对象。(所有对象 共享 一份 静态成员数据)。

static修饰的成员 定义类的时候 必须分配空间。

static修饰的静态成员数据 必须类中定义 类外初始化。

class Data
{
public:
int a;//普通成员数据
//类中定义
static int b;//静态成员数据
};
//类外初始化
int Data::b= 100 ;//不用加static
void test01()
{
//静态成员数据 通过类名称直接访问(属于类)
cout<<Data::b<<endl;

//静态成员数据 通过对象访问(共享)
Data ob1;
cout<<ob1.b<<endl;//100
ob1.b = 200 ;
Data ob2;
ob2.b = 300 ;
cout<<Data::b<<endl;//300
}

案例 1 :使用静态成员数据 统计对象的个数

class Data2
{
public:
int mA;
static int count;
public:
Data2()
{
count++;

Data2(int a)
{
mA = a;
count++;
}

Data2(const Data2 &ob)
{
count++;
}
~Data2()
{
count--;
}
};
int Data2::count= 0 ;
void test02()
{
Data2 ob1;
Data2 ob2( 10 );
Data2 ob3 = ob2;
cout<<"对象个数:"<<Data2::count<<endl;//3
{
Data2 ob4;
Data2 ob5;
cout<<"对象个数:"<<Data2::count<<endl;//5
}
cout<<"对象个数:"<<Data2::count<<endl;//3
}
}

14.9.2 静态成员函数

静态成员函数 是属于类 而不是对象(所有对象 共享)

class Data
{
static void func()//静态成员函数
{
}
}

静态成员函数 可以直接通过类名称访问

}

静态成员函数内 只能操作静态成员数据。

14.9.3 单例模式设计

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模

式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系

统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案

#include <iostream>
using namespace std;
class SingleTon//单例模式
{
//构造私有化 防止实例化其他对象
private:
SingleTon(){
count= 0 ;
cout<<"构造"<<endl;
}

SingleTon(const SingleTon &ob){
count= 0 ;
}
~SingleTon()
{
cout<<"析够"<<endl;
}
private:
//const防止p 在类内部 被修改指向
static SingleTon * const p;//保存唯一的实例地址
int count;//统计任务执行次数
public:
static SingleTon * getSingleTon(void)//获取唯一的实例地址
{
return p;
}

//用户自定义 任务函数

void printString(char *str)
{
count++;
cout<<"当前第"<<count<<"次任务打印:"<<str<<endl;
}

};
SingleTon *const SingleTon::p = new SingleTon;//创建唯一的实例

int main(int argc, char *argv[])
{
//获取单例的地址
SingleTon *p1 =SingleTon::getSingleTon();
p1->printString("离职证明1");
p1->printString("学历证明1");
p1->printString("学位证明1");
p1->printString("身份证明1");

SingleTon *p2 =SingleTon::getSingleTon();
p2->printString("离职证明2");
p2->printString("学历证明2");
p2->printString("学位证明2");
p2->printString("身份证明2");
return 0 ;
}

14.10 c++面向对象模型

14.10.1 成员变量和函数的存储

c++实现了“封装”,“数据”和“处理数据的操作(函数)”是分开存储的。 c++中的非静态数据成员直接内含在

类对象中,成员函数虽然内含在class声明之内,却不出现在对象中。 每一个非内联成员函数只会诞生一

份函数实例。

sizeof(Data1)的大小只是 a 和 b所占空间大小(类的对象所占空间大小)。

class MyClass01{
public:
int mA;
};
class MyClass02{
public:
int mA;
static int mB;
};
class MyClass03{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
public:
int mA;
static int mB;
};
class MyClass04{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
static void ShowMyClass(){
cout << "hello world!" << endl;
}
public:
int mA;
static int mB;
};
int main(){
MyClass01 mclass01;
MyClass02 mclass02;
MyClass03 mclass03;
MyClass04 mclass04;
cout << "MyClass01:" << sizeof(mclass01) << endl; //4
//静态数据成员并不保存在类对象中
cout << "MyClass02:" << sizeof(mclass02) << endl; //4
//非静态成员函数不保存在类对象中
cout << "MyClass03:" << sizeof(mclass03) << endl; //4
//静态成员函数也不保存在类对象中
cout << "MyClass04:" << sizeof(mclass04) << endl; //4
return 0 ;
}

通过上面的案例,我们可以的得出:C++类对象中的变量和函数是分开存储。

14.10.2 this指针

1 、this指针工作原理

通过上例我们知道,c++的数据和操作也是分开存储,并且每一个非内联成员函数只会诞生一份函数实

例,也就是说多个同类型的对象会共用一块代码 那么问题是:这一块代码是如何区分那个对象调用自己

的呢?

c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。

成员函数通过this指针即可知道操作的是那个对象的数据。This指针是一种隐含指针,它隐含于每个类的

非静态成员函数中。This指针无需定义,直接使用即可。

注意:静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量

2 、函数形参和成员同名可以使用this指针解决。

3 、this来完成链式操作

14.10.3 const修饰成员函数

用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任

何普通成员变量, 当成员变量类型符前用mutable修饰时例外

class Data
{
public:
int a;
int b;
mutable int c;
public:
Data(int a, int b,int c)
{
this->a = a;
this->b = b;
this->c = c;
}
//const 修饰成员函数为只读(该成员函数不允许对 成员数据 赋值) mutable修饰的成员除外
void showData(void) const
{
//a = 100;//err
c= 100 ;
cout<<a<<" "<<b<<" "<<c<<endl;
}
};
int main()
{
Data ob1( 10 , 20 , 30 );
ob1.showData();
}

14.11 友元

类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需

要在类的外部访问类的私有成员,怎么办?

解决方法是使用友元函数,友元函数是一种特权函数,c++允许这个特权函数访问私有成员。这一点从

现实生活中也可以很好的理解: 比如你的家,有客厅,有你的卧室,那么你的客厅是Public的,所有来

的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的闺蜜

好基友进去。 程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。

14.11.1 友元的语法

使用friend关键字声明友元。

friend关键字只出现在声明处,一个函数或者类 作为了另一个类的友元 那么这个函数或类 就可以直接

访问 另一个类的私有数据。

友元 重要用在运算符重载上。

14.11.2 普通全局函数作为类的友元

#include <string>
using namespace std;
class Room
{
friend void visiting01(Room &room);
private:
string bedRoom;//卧室
public:
string setingRoom;//客厅
public:
Room(string bedRoom, string setingRoom)
{
this->bedRoom = bedRoom;
this->setingRoom = setingRoom;
}
};
//普通全局函数
void visiting01(Room &room)
{
cout<<"访问了"<<room.setingRoom<<endl;
cout<<"访问了"<<room.bedRoom<<endl;
}

int main(int argc, char *argv[])
{
Room room("卧室","客厅");
visiting01(room);
return 0 ;
}

14.11.3 类的某个成员函数 作为另一个类的友元

class Room;//向前声明 只能说明类名称
class goodGay
{
public:
void visiting01(Room &room);
void visiting02(Room &room);
};

class Room
{
friend void goodGay::visiting02(Room &room);
private:
string bedRoom;//卧室
public:
string setingRoom;//客厅
public:
Room(string bedRoom, string setingRoom)
{
this->bedRoom = bedRoom;
this->setingRoom = setingRoom;
}
};
void goodGay::visiting01(Room &room)
{
cout<<"访问了"<<room.setingRoom<<endl;
//cout<<"访问了"<<room.bedRoom<<endl;
}

void goodGay::visiting02(Room &room)
{
cout<<"好基友张三访问了"<<room.setingRoom<<endl;
cout<<"好基友张三访问了"<<room.bedRoom<<endl;
}

int main(int argc, char *argv[])
{
Room room("卧室","客厅");
goodGay ob;
ob.visiting01(room);
ob.visiting02(room);
return 0 ;
}

14.11.4 整个类作为 另一个类的友元

这个类的所有成员函数 都可以访问另一个类的私有数据.

class Room;//向前声明 只能说明类名称
class goodGay
{
public:
void visiting01(Room &room);
void visiting02(Room &room);
};

class Room
{
friend class goodGay;
private:
string bedRoom;//卧室
public:
string setingRoom;//客厅
public:
Room(string bedRoom, string setingRoom)

{


this->bedRoom = bedRoom;
this->setingRoom = setingRoom;
}
};

void goodGay::visiting01(Room &room)
{
cout<<"访问了"<<room.setingRoom<<endl;
cout<<"访问了"<<room.bedRoom<<endl;
}

void goodGay::visiting02(Room &room)
{
cout<<"好基友访问了"<<room.setingRoom<<endl;
cout<<"好基友访问了"<<room.bedRoom<<endl;
}

int main(int argc, char *argv[])
{
Room room("卧室","客厅");
goodGay ob;
ob.visiting01(room);
ob.visiting02(room);
return 0 ;
}

14.11.5 友元的注意事项

1 .友元关系不能被继承。

2 .友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。

3 .友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友

14.11.6 友元案例(遥控器的类)

请编写电视机类,电视机有开机和关机状态,有音量,有频道,提供音量操作的方法,频道操作的方

法。由于电视机只能逐一调整频道,不能指定频道,增加遥控类,遥控类除了拥有电视机已有的功能,

再增加根据输入调台功能。

提示:遥控器可作为电视机类的友元类

#include <iostream>
using namespace std;

class TV;
//遥控器的类作为TV的友元
class Remote
{
private:
TV *p;
public:
Remote(TV *p);
void offOrOn(void);
void upVolume(void);
void downVolume(void);
void upChannel(void);


void downChannel(void);
void showTv(void);
void setChannel(int channel);
};

class TV
{
friend class Remote;
enum{OFF,ON};
enum{minVol, maxVol= 10 };
enum{minChan, maxChan= 25 };
private:
int state;
int volume;
int channel;
public:
TV()
{
state = OFF;
volume = minVol;
channel = minChan;
}

void offOrOn(void);
void upVolume(void);
void downVolume(void);
void upChannel(void);
void downChannel(void);
void showTv(void);

};

int main(int argc, char *argv[])
{
TV tv;
Remote re(&tv);
re.offOrOn();
re.upVolume();
re.upVolume();
re.setChannel( 10 );
re.showTv();
return 0 ;
}

void TV::offOrOn()
{
state = (state==OFF?ON:OFF);
}

void TV::upVolume()
{
if(volume == maxVol)
{
cout<<"音量已经最大"<<endl;
return;
}


volume++;
}

void TV::downVolume()
{
if(volume ==minVol)
{
cout<<"音量已经最小"<<endl;
return;
}
volume--;
}

void TV::upChannel()
{
if(channel == maxChan)
{
cout<<"频道已经最大"<<endl;
return;
}
channel++;
}

void TV::downChannel()
{
if(channel == minChan)
{
cout<<"频道已经最小"<<endl;
return;
}
channel--;
}
void TV::showTv()
{
cout<<"当前电视机的状态:"<<(state==OFF?"关":"开")<<endl;
cout<<"当前电视机的音量:"<<volume<<endl;
cout<<"当前电视机的频道:"<<channel<<endl;
}
Remote::Remote(TV *p)
{
this->p = p;
}
void Remote::offOrOn()
{
p->offOrOn();
}
void Remote::upVolume()
{
p->upVolume();
}
void Remote::downVolume()
{
p->downVolume();
}
void Remote::upChannel()
{
p->upChannel();
}

void Remote::downChannel()
{
p->downChannel();
}

void Remote::showTv()
{
p->showTv();
}
void Remote::setChannel(int channel)
{
if(channel>=TV::minChan && channel<=TV::maxChan)
{
p->channel = channel;
}
else
{
cout<<"频道"<<channel<<"不在有效范围内"<<endl;
}
}

14.11.7 设计动态数组类案例

14.12 运算符重载

14.12.1 运算符重载基本概念

运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

语法: 定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算

符。

思路:

1 、弄懂运算符的运算对象的个数。(个数决定了 重载函数的参数个数)

2 、识别运算符左边的运算对象 是类的对象 还是其他.

类的对象:全局函数实现(不推荐) 成员函数实现(推荐,少一个参数)

其他:只能是全局函数实现

14.12.2 重载<<运算符(全局函数实现)

如果使用全局函数 重载运算符 必须将全局函数设置成友元。

#include <string>
using namespace std;
class Person
{
friend ostream& operator<<(ostream &out, Person &ob);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score):num(num),name(name),score(score){}

};
/全局函数重载operator<<
ostream& operator<<(ostream &out, Person &ob)
{
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
int main(int argc, char *argv[])
{
Person lucy( 100 ,"lucy", 99.8f);
Person bob( 101 ,"bob", 99.8f);
cout<<lucy<<bob<<endl;//operator<<(cout, lucy);

return 0 ;
}

14.12.3 重载>>运算符(全局函数实现)

#include <iostream>
#include <string>
using namespace std;
class Person
{
friend ostream& operator<<(ostream &out, Person &ob);
friend istream& operator>>(istream &in, Person &ob);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score):num(num),name(name),score(score){}

};

//全局函数重载operator<<
ostream& operator<<(ostream &out, Person &ob)
{
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
//全局函数重载operator>>
istream& operator>>(istream &in, Person &ob)
{
in>>ob.num>>ob.name>>ob.score;
return in;
}
int main(int argc, char *argv[])
{

Person lucy;
Person bob;

cin>>lucy>>bob;
cout<<lucy<<bob<<endl;

return 0 ;
}

14.12.4 可以重载的运算符

14.12.5 重载+运算符(全局函数实现)

#include <iostream>
#include <string>
using namespace std;
class Person
{
friend ostream& operator<<(ostream &out, Person ob);
friend istream& operator>>(istream &in, Person &ob);
friend Person operator+(Person &ob1, Person &ob2);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score):num(num),name(name),score(score){}

};

//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
//全局函数重载operator>>
istream& operator>>(istream &in, Person &ob)
{
in>>ob.num>>ob.name>>ob.score;
return in;
}
//全局函数重载+运算符
Person operator+(Person &ob1, Person &ob2)
{
Person tmp;
tmp.num = ob1.num+ob2.num;
tmp.name = ob1.name+ob2.name;
tmp.score = ob1.score+ob2.score;

return tmp;
}
int main(int argc, char *argv[])
{

Person lucy( 100 ,"lucy", 88.8);
Person bob( 101 ,"bob",99.9);

cout<<lucy+bob<<endl;

return 0 ;
}

14.12.6 重载+运算符(成员函数实现 推荐)

#include <iostream>
#include <string>
using namespace std;
class Person
{
friend ostream& operator<<(ostream &out, Person ob);
friend istream& operator>>(istream &in, Person &ob);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score):num(num),name(name),score(score){}
//成员函数重载+运算符
Person operator+(Person &ob)
{
Person tmp;
tmp.num = num+ob.num;
tmp.name = name+ob.name;
tmp.score = score+ob.score;
return tmp;
}
};

//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
//全局函数重载operator>>
istream& operator>>(istream &in, Person &ob)
{
in>>ob.num>>ob.name>>ob.score;
return in;
}
int main(int argc, char *argv[])
{
Person lucy( 100 ,"lucy", 88.8);
Person bob( 101 ,"bob",99.9);
//lucy+bob ===> lucy.operator+(bob)
//cout<<lucy.operator +(bob)<<endl;
cout<<lucy+bob<<endl;

return 0 ;
}

14.12.7 重载==运算符符(成员函数实现 推荐)

#include <iostream>
#include <string>
using namespace std;
class Person
{
friend ostream& operator<<(ostream &out, Person ob);
friend istream& operator>>(istream &in, Person &ob);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score):num(num),name(name),score(score){}
//成员函数重载+运算符
Person operator+(Person &ob)
{
Person tmp;
tmp.num = num+ob.num;
tmp.name = name+ob.name;
tmp.score = score+ob.score;
return tmp;
}
//成员函数重载==运算符
bool operator==(Person &ob)
{
if(num==ob.num && name==ob.name && score==ob.score)
return true;
return false;
}
};

//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
//全局函数重载operator>>
istream& operator>>(istream &in, Person &ob)
{
in>>ob.num>>ob.name>>ob.score;
return in;
}

int main(int argc, char *argv[])
{

Person lucy( 100 ,"lucy", 88.8);
Person bob( 100 ,"lucy",88.8);

if(lucy == bob)
{
cout<<"相等"<<endl;

else
{
cout<<"不相等"<<endl;
}

return 0 ;
}

14.12.8 重载++/–运算符

重载的++和–运算符有点让人不知所措,因为我们总是希望能根据它们出现在所作用对象的前面还是后

面来调用不同的函数。解决办法很简单,例如当编译器看到++a(前置++),它就调用operator++(a),当编

译器看到a++(后置++),它就会去调用operator++(a,int).

++a(前置++),它就调用operator++(a),

a++(后置++),它就会去调用operator++(a,int)

–a(前置–),它就调用operator–(a),

a–(后置–),它就会去调用operator–(a,int)

案例 1 :重载后置++

}

类名称 operator++(int)
{
//先保存 旧的值old
//自增++
return old;//返回旧值
}

/#include <iostream>
#include <string>
using namespace std;
class Person
{
friend ostream& operator<<(ostream &out, Person ob);
friend istream& operator>>(istream &in, Person &ob);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score):num(num),name(name),score(score){}
//成员函数重载+运算符
Person operator+(Person &ob)
{
Person tmp;
tmp.num = num+ob.num;
tmp.name = name+ob.name;
tmp.score = score+ob.score;
return tmp;
}

//成员函数重载==运算符

bool operator==(Person &ob)
{
if(num==ob.num && name==ob.name && score==ob.score)
return true;
return false;
}

//重载后置++
Person operator++(int)
{
//保存旧值
Person old = *this;
//++
this->num++;
this->name= this->name+this->name;
this->score++;

//返回值旧值
return old;
}
};

//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
//全局函数重载operator>>
istream& operator>>(istream &in, Person &ob)
{
in>>ob.num>>ob.name>>ob.score;
return in;
}

int main(int argc, char *argv[])
{

Person lucy( 100 ,"lucy", 88.8);
Person bob;

bob = lucy++;
cout<<bob<<endl;
cout<<lucy<<endl;

return 0 ;
}

案例 2 :重载前置++

类名称 operator++()
{
//自增++
return *this;
}
#include <iostream>
#include <string>
using namespace std;
class Person
{
friend ostream& operator<<(ostream &out, Person ob);
friend istream& operator>>(istream &in, Person &ob);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name,float score):num(num),name(name),score(score){}
//成员函数重载+运算符
Person operator+(Person &ob)
{
Person tmp;
tmp.num = num+ob.num;
tmp.name = name+ob.name;
tmp.score = score+ob.score;
return tmp;
}

//成员函数重载==运算符
bool operator==(Person &ob)
{
if(num==ob.num && name==ob.name && score==ob.score)
return true;
return false;
}

//重载后置++
Person operator++(int)
{
//保存旧值
Person old = *this;
//++
this->num++;
this->name= this->name+this->name;
this->score++;

//返回值旧值
return old;
}

//重载前置++
Person operator++()
{
//++
this->num++;
this->name= this->name+this->name;
this->score++;

return *this;
}
};

//全局函数重载operator<<
ostream& operator<<(ostream &out, Person ob)
{
out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}
//全局函数重载operator>>
istream& operator>>(istream &in, Person &ob)
{
in>>ob.num>>ob.name>>ob.score;
return in;
}

int main(int argc, char *argv[])
{

Person lucy( 100 ,"lucy", 88.8);
Person bob;

bob = ++lucy;
cout<<bob<<endl;
cout<<lucy<<endl;

return 0 ;
}

14.12.9 设计MyString案例

#include <iostream>
#include <string.h>
#include <string>
using namespace std;

class MyString
{
friend ostream& operator<<(ostream &out, MyString ob);
friend istream& operator>>(istream &in, MyString &ob);
private:
char *str;
int size;
public:
MyString();
MyString(char *str);
MyString(const MyString &ob);
~MyString();
int getSize();

//成员函数重载[]运算符
char& operator[](int pos);

//重载+运算符
MyString operator+(MyString &ob);
MyString operator+(char *str);

//重载赋值运算符(只有指针成员operator=必须深拷贝)

MyString& operator=(MyString ob);
MyString& operator=(char *str);

//重载关系运算符>
bool operator>(MyString ob);
bool operator>(char *str);

};

//全局函数重载<<运算符

ostream& operator<<(ostream &out, MyString ob)
{
out<<ob.str;
return out;
}

istream& operator>>(istream &in, MyString &ob)
{
char buf[ 128 ]="";
cin>>buf;

//判断ob.str是否为NULL
if(ob.str != NULL)
{
delete [] ob.str;
ob.str=NULL;
}

ob.size = strlen(buf);
ob.str = new char[ob.size+ 1 ];
memset(ob.str, 0 , ob.size+ 1 );
strcpy(ob.str, buf);

return in;
}
int main(int argc, char *argv[])
{
MyString str1="hello";
str1[ 1 ]='H';
cout<<str1[ 1 ]<<endl;
cout<<str1<<endl;

MyString str2;
cin>>str2;
cout<<str2<<endl;

MyString str3 = "hello";
MyString str4 = "world";
cout<<str3+str4<<endl;
cout<<str3+"world"<<endl;

MyString str5="hello world";
MyString str6="world";
str6 = str5;
cout<<str6<<endl;
str6="hello str6";
cout<<str6<<endl;


if(str5 > str6)
{
cout<<"大于"<<endl;
}
else
{
cout<<"不大于"<<endl;
}

return 0 ;
}

MyString::MyString()
{
str=NULL;
size= 0 ;
}

MyString::MyString(char *str)
{
this->str = new char[strlen(str)+ 1 ];
memset(this->str, 0 ,strlen(str)+ 1 );
size = strlen(str);
strcpy(this->str,str);
}

MyString::MyString(const MyString &ob)
{
size = ob.size;
str = new char[size+ 1 ];
memset(str, 0 ,size+ 1 );
strcpy(str,ob.str);
}

MyString::~MyString()
{
if(str != NULL)
{
delete [] str;
str=NULL;
}
}

int MyString::getSize()
{
return size;
}

char& MyString::operator[](int pos)
{
if(pos< 0 || pos>=size)
{
cout<<"下标越界"<<endl;
exit(- 1 );
}

return str[pos];


}

MyString MyString::operator+(MyString &ob)
{
MyString tmp;
tmp.size = this->size+ob.size;
tmp.str = new char[tmp.size + 1 ];
memset(tmp.str, 0 , tmp.size+ 1 );

strcpy(tmp.str, this->str);
strcat(tmp.str, ob.str);

return tmp;
}

MyString MyString::operator+(char *str)
{
MyString tmp;
tmp.size = this->size+strlen(str);
tmp.str = new char[tmp.size + 1 ];
memset(tmp.str, 0 , tmp.size+ 1 );

strcpy(tmp.str, this->str);
strcat(tmp.str, str);

return tmp;
}

MyString &MyString::operator=(MyString ob)
{
//str5=str4
if(str != NULL)
{
delete [] str;
str=NULL;
}

size = ob.size;
str = new char[size+ 1 ];
memset(str, 0 ,size+ 1 );
strcpy(str, ob.str);

return *this;
}

MyString &MyString::operator=(char *str)
{
//str5=str4
if(this->str != NULL)
{
delete [] this->str;
this->str=NULL;
}

size = strlen(str);
this->str = new char[size+ 1 ];
memset(this->str, 0 ,size+ 1 );
strcpy(this->str, str);

return *this;
}

bool MyString::operator>(MyString ob)
{
if(str==NULL || ob.str == NULL)
{
exit(- 1 );
}
if(strcmp(str, ob.str) > 0 )
return true;
return false;
}
bool MyString::operator>(char *str)
{
if(this->str==NULL || str == NULL)
{
exit(- 1 );
}
if(strcmp(this->str, str) > 0 )
return true;
return false;
}

14.12.10 重载函数调用运算符

重载()运算符 一般用于 为算法 提供策略。

当对象和()结合 会触发 重载函数调用运算符

#include <iostream>

using namespace std;
//仿函数
class Print
{
public:
//重载函数调用运算符
void operator()(char *str)
{
cout<<str<<endl;
}
};
int main(int argc, char *argv[])
{
Print ob;
//对象和()结合触发operator()调用
ob("hello Print");

//Print()为匿名对象
Print()("hello print");
return 0 ;
}

14.12.11 智能指针(指针运算符(*、->)重载)

智能指针:解决 堆区空间的对象 释放问题

#include <iostream>

using namespace std;
class Data
{
public:
Data()
{
cout<<"Data的无参构造"<<endl;
}
~Data()
{
cout<<"Data的析够"<<endl;
}
void func()
{
cout<<"Data的func函数"<<endl;
}
};

//智能指针
class SmartPointer
{
private:
Data *p;
public:
SmartPointer(){}
SmartPointer(Data *p)
{
this->p = p;
}
~SmartPointer()
{
delete p;
}
// 重载->运算符
Data* operator->()
{
return p;
}
Data& operator*()
{
return *p;
}
};
int main(int argc, char *argv[])
{
SmartPointer sp(new Data);

//sp.operator ->()->func();
sp->func();

(*sp).func();

return 0 ;
}

14.12.12 不要重载 && ||

不能重载operator&& 和 operator|| 的原因是,无法在这两种情况下实现内置操作符的完整语义。说得

更具体一些,内置版本版本特殊之处在于:内置版本的&&和||首先计算左边的表达式,如果这完全能够

决定结果,就无需计算右边的表达式了–而且能够保证不需要。我们都已经习惯这种方便的特性了。 我

们说操作符重载其实是另一种形式的函数调用而已,对于函数调用总是在函数执行之前对所有参

数进行求值。

class Complex{
public:
Complex(int flag){
this->flag = flag;
}
Complex& operator+=(Complex& complex){
this->flag = this->flag + complex.flag;
return *this;
}
bool operator&&(Complex& complex){
return this->flag && complex.flag;
}
public:
int flag;
};
int main(){
Complex complex1( 0 ); //flag 0
Complex complex2( 1 ); //flag 1
//原来情况,应该从左往右运算,左边为假,则退出运算,结果为假
//这边却是,先运算(complex1+complex2),导致,complex1的flag变为complex1+complex2
的值, complex1.a = 1
// 1 && 1
//complex1.operator&&(complex1.operator+=(complex2))
if (complex1 && (complex1 += complex2)){
//complex1.operator+=(complex2)
cout << "真!" << endl;
}
else{
cout << "假!" << endl;
return 0 ;
}

C++高级部分

第十五章 继承

15.1 继承和派生

15.1.1 为什么需要继承

提高代码重用,提高开发效率。

15.1.2 继承的基本概念

c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不

仅拥有旧类的成员,还拥有新定义的成员。 一个B类继承于A类,或称从类A派生类B。这样的话,类A成

为基类(父类), 类B成为派生类(子类)。 派生类中的成员,包含两大部分: 一类是从基类继承过来

的,一类是自己增加的成员。 从基类继承过过来的表现其共性,而新增的成员体现了其个性。

15.1.3 派生类的定义

class 父类{};
class 子类:继承方式 父类名
{
//新增子类数据
};

继承方式:private protected public(推荐)

所有父类私有在子类中不可访问,公共继承 保持不变,保护继承变保护,私有继承变私有。

class Base
{
private:
int a;
protected:
int b;
public:
int c;
};
class Son:public Base
{
//子类中 能访问 protected b public c
public:
void func(void)
{
cout<<b<<c<<endl;
//cout<<a<<endl;//不可访问
}
};
int main()
{
Son ob;
//cout<<ob.b<<endl;//类外无法访问
cout<<ob.c<<endl;
}

15.2 继承中的构造和析构

15.2.1 子类的构造析构顺序

class Base
{
public:
Base()
{
cout<<"父类构造"<<endl;
}
~Base()
{
cout<<"父类析够"<<endl;
}
};
class Other
{
public:
Other()
{
cout<<"Other构造"<<endl;
}
~Other()
{
cout<<"Other析够"<<endl;
}
};

class Son:public Base
{
public:
Other ob;
public:
public:
Son()
{

cout<<"Son构造"<<endl;
}
~Son()
{
cout<<"Son析够"<<endl;
}
};

int main()
{
Son ob;
return 0 ;
}

15.2.2 子类调用成员对象、父类的有参构造

子类实例化对象时 会自动调用 成员对象、父类的默认构造。

子类实例对象时 必须使用初始化列表 调用成员对象、父类的有参构造。

初始化列表时:父类写类名称 成员对象用对象名

#include <iostream>
using namespace std;
class Base
{
public:
int a;
public:
Base()
{
cout<<"父类默认构造"<<endl;
}
Base(int a)
{
this->a = a;
cout<<"父类有参构造"<<endl;
}
~Base()
{
cout<<"父类析够"<<endl;
}
};

class Other
{
public:
int b;
public:
Other()
{
cout<<"Other默认构造"<<endl;
}
Other(int b)
{
this->b = b;
cout<<"Other有参构造"<<endl;
}
~Other()
{
cout<<"Other析够"<<endl;
}
};

class Son:public Base
{
public:
Other ob;
int c;
public:
Son()
{
cout<<"Son构造"<<endl;
}
//父类写类名称 成员对象用对象名
Son(int a, int b, int c):Base(a), ob(b)
{
this->c = c;
cout<<"Son有参构造"<<endl;
}
~Son()
{
cout<<"Son析够"<<endl;
}
};
int main(int argc, char *argv[])
{
Son ob( 10 , 20 , 30 );
return 0 ;
}

15.3 子类和父类的同名处理

同名成员 最简单 最安全的处理方式:加作用域

15.3.1 子类和父类 同名成员数据

子类默认优先访问 子类的同名成员

必须加父类作用域 访问父类的同名成员

class Base
{
public:
int a;
public:
Base(int a)
{
this->a = a;
}
};
class Son:public Base
{
public:
int a;
Son(int x, int y):Base(x)
{
a = y;
}
};

int main()
{
Son ob( 10 , 20 );
//子类默认优先访问 子类的同名成员
cout<<ob.a<<endl;//20
//必须加父类作用域 访问父类的同名成员
cout<<ob.Base::a<<endl;//10
return 0 ;
}

15.3.2 子类和父类 同名成员函数

class Base
{
public:
void fun01()
{
cout<<"Base 无参的fun01"<<endl;
}
};
class Son:public Base
{
public:
void fun01()
{
cout<<"Son 无参的fun01"<<endl;
}
};

int main()
{
Son ob;
//子类默认优先访问 子类的同名成员
ob.fun01();
//必须加父类作用域 访问父类的同名成员
ob.Base::fun01();
return 0 ;
}

15.3.3 子类 重定义 父类的同名函数

重载:无继承,同一作用域,参数的个数不同、顺序不同、类型不同 都可重载

重定义:有继承, 子类 重定义 父类的同名函数(参数可以不同)(非虚函数)子类一旦 重定义了父类

的同名函数(不管参数是否一致),子类中都将屏蔽父类所有的同名函数。

class Base
{
public:
void fun01()
{
cout<<"Base 无参的fun01"<<endl;
}
void fun01(int a)
{
cout<<"Base 的fun01 int"<<endl;
}

void fun01(int a, int b)
{
cout<<"Base 的fun01 int int"<<endl;
}
};

class Son:public Base
{
public:
void fun01(string a)
{
cout<<"Son 的fun01 char"<<endl;
}
};

int main()
{
Son ob;
ob.fun01("hello");
ob.fun01();
ob.fun01( 10 );
ob.fun01( 10 , 20 );
return 0 ;
}

需要将父类的作用域 才能识别 屏蔽的函数

15.4 子类不能继承父类的成员

不是所有的函数都能自动从基类继承到派生类中。 构造函数 和 析构函数 用来处理对象的创建和析构操

作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继

承,必须为每一个特定的派生类分别创建。 另外 operator= 也不能被继承,因为它完成类似构造函数的

行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意

味着对其派生类依然有效。 在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。

15.5 多继承

15.5.1 多继承的概念

我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争

议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。

15.5.2 多继承的格式

class 父类 1 {};
class 父类 2 {};
class 子类:继承方式 1 父类 1 , 继承方式 2 父类 2
{
//新增子类数据
};

子类就拥有了父类 1 ,父类 2 的大部分数据.

class Base1
{
public:
int a;
};
class Base2
{
public:
int b;
};
class Son:public Base1, public Base2
{
public:

};
int main()
{
Son ob;
cout<<ob.a<<" "<<ob.b<<endl;
return 0 ;
}

15.5.3 多继承中的同名成员处理

如果多继承中 遇到同名成员 需要加父类作用域解决。

class Base1
{
public:
int a;
Base1(int a):a(a){}
};
class Base2
{
public:
int a;
Base2(int a):a(a){}
};
class Son:public Base1, public Base2
{
public:
int a;
Son(int a, int b,int c):Base1(a),Base2(b),a(c){}
};
int main()
{
Son ob( 10 , 20 , 30 );
cout<<ob.a<<endl;//子类a
cout<<ob.Base1::a<<endl;//Base1 a
cout<<ob.Base2::a<<endl;//Base2 a
return 0 ;
}

15.6 菱形继承

菱形继承:有公共祖先的继承 叫菱形继承。

最底层的子类 数据 会包含多份(公共祖先的数据)

class Animal
{
public:
int data;
};
class Sheep :public Animal{};
class Tuo :public Animal {};
class SheepTuo:public Sheep,public Tuo{};
int main()
{
SheepTuo ob;
memset(&ob, 0 , sizeof(SheepTuo));
//cout << ob.data << endl;//二义性
cout << ob.Sheep::data << endl;
cout << ob.Tuo::data << endl;
}

怎么才能只要公共祖先的一份数据呢?

15.7 虚继承

虚继承 解决 菱形继承中 多份公共祖先数据的问题。

15.7.1 虚继承的方式

在继承方式 前加virtual修饰,子类虚继承父类 子类只会保存一份公共数据。

#include<iostream>
#include<string.h>
using namespace std;
class Animal
{
public:
int data;
};
class Sheep :virtual public Animal{
};
class Tuo :virtual public Animal {
};
class SheepTuo:public Sheep,public Tuo{};
int main()
{
SheepTuo ob;
cout << ob.data << endl;
return 0 ;
}

15.7.2 虚继承的实现原理(VS有效)

1 、打开命令行开发模式

2 、找到类所在的源文件路劲

3 、在命令行中 导出类的布局

cl /d1 reportSingleClassLayout Animal main.cpp

Animal布局:

Sheep布局:

Tuo布局:

SheepTuo布局:

虚继承 会在子类中产生 虚基类指针(vbptr) 指向 虚基类表(vbtable), 虚基类表纪录的是通过该指针访

问公共祖先的数据的偏移量。

注意:

虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的.工程

开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多

重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。

第十六章 多态

多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。 多态性(polymorphism)提供

接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组

织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新

的功能时也能扩展。

静态多态(编译时多态,早绑定):函数重载、运算符重载、重定义

动态多态(运行时多态,晚绑定):虚函数

16.1 虚函数

16.1.1 知识点引入

父类指针(引用)保存 子类空间地址的目的 就是让算法通用。

16.1.2 父类指针 保存 子类空间地址(带来的问题)

class Animal
{
public:
void speak(void)
{
cout<<"动物在说话"<<endl;
}
};
class Dog:public Animal
{
public:
void speak(void)
{
cout<<"狗在汪汪"<<endl;
}
};
int main()
{
Animal *p = new Dog;
p->speak();//动物在说话
}

其实用户的需求:p->speak 希望等到的是“狗在汪汪” 而不是“动物在说话”。原因在此:

16.1.3 虚函数的定义

成员函数前加virtual修饰

class Animal
{
public:
//虚函数
virtual void speak(void)
{
cout<<"动物在说话"<<endl;

}

};


class Dog:public Animal
{
public:
//子类重写 父类的虚函数:函数名、返回值类型、参数类型个数顺序 必须完全一致
virtual void speak(void)
{
cout<<"狗在汪汪"<<endl;
}
};
class Cat:public Animal
{
public:
//子类重写 父类的虚函数
void speak(void)
{
cout<<"猫在喵喵"<<endl;
}
};
int main()
{
Animal *p1 = new Dog;
p1->speak();//"狗在汪汪"
Animal *p2 = new Cat;
p2->speak();//"猫在喵喵"
}

多态条件:有继承、子类重写父类的虚函数,父类指针 指向子类空间。

16.1.4 虚函数的动态绑定机制

Animal的类的结构:

如果一个类中的成员函数 被virtual修饰,那么这个函数就是虚函数。类就会产生一个虚函数指针

(vfptr)指向了一张虚函数表(vftable)。如果这个类 没有涉及到继承, 这时虚函数表中 纪录就是当

前虚函数入口地址。

Dog的类存结构:

#include <iostream>

using namespace std;
class Animal
{
public:
//虚函数
virtual void speak(void)
{
cout<<"动物在说话"<<endl;
}
};
class Dog:public Animal

{
public:
#if 1
//子类重写 父类的虚函数
void speak(void)
{
cout<<"狗在汪汪"<<endl;
}
#endif
};
class Cat:public Animal
{
public:
//子类重写 父类的虚函数
void speak(void)
{
cout<<"猫在喵喵"<<endl;
}
};
void AnimalSpeak(Animal *p)
{
p->speak();
return;
}

int main(int argc, char *argv[])
{
AnimalSpeak(new Dog);//狗在汪汪
AnimalSpeak(new Cat);//猫在喵喵

return 0 ;
}

16.1.5 重载、重定义、重写的区别

重载:同一作用域,同名函数,参数的顺序、个数、类型不同 都可以重载。函数的返回值类型不能作为

重载条件(函数重载、运算符重载)

重定义:有继承,子类 重定义 父类的同名函数(非虚函数), 参数顺序、个数、类型可以不同。子类

的同名函数会屏蔽父类的所有同名函数(可以通过作用域解决)

重写(覆盖):有继承,子类 重写 父类的虚函数。返回值类型、函数名、参数顺序、个数、类型都必须

一致。

16.2 纯虚函数

通过上面的讲解发现,如果基类一定派生出子类,而子类一定会重写父类的虚函数,那么父类的虚函数

中的函数体感觉是无意义,可不可以不写父类虚函数的函数体呢?可以的,那就必须了解纯虚函数。

16.2.1 纯虚函数的定义方式

class Animal
{
public:
//纯虚函数
virtual void speak(void)= 0 ;
};

一旦类中有纯虚函数,那么这个类 就是抽象类。

抽象类 不能实例化 对象。(Animal ob;错误)

抽象类 必须被继承 同时 子类 必须重写 父类的所有纯虚函数,否则 子类也是抽象类。

#include <iostream>
using namespace std;
//有 纯虚函数的类 为抽象类
class Animal
{
public:
//纯虚函数
virtual void speak(void)= 0 ;
};
class Dog:public Animal
{
public:
//子类一定要重写 父类的所有纯虚函数
void speak(void)
{
cout<<"狗在汪汪"<<endl;
}
};
int main(int argc, char *argv[])
{
//Animal ob;//err 抽象类不能实例化对象
Animal *p = new Dog;
p->speak();//"狗在汪汪"
return 0 ;
}

抽象类主要的目的 是设计 类的接口:

16.2.2 虚函数案例-饮品制作

#include <iostream>

using namespace std;
//抽象制作饮品
class AbstractDrinking{
public:
//烧水
virtual void Boil() = 0 ;
//冲泡
virtual void Brew() = 0 ;
//倒入杯中
virtual void PourInCup() = 0 ;
//加入辅料
virtual void PutSomething() = 0 ;
//规定流程
void MakeDrink(){
this->Boil();
Brew();
PourInCup();
PutSomething();
}
};

//制作咖啡
class Coffee : public AbstractDrinking{
public:
//烧水
virtual void Boil(){
cout << "煮农夫山泉!" << endl;
}
//冲泡
virtual void Brew(){
cout << "冲泡咖啡!" << endl;


}

//倒入杯中

virtual void PourInCup(){
cout << "将咖啡倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething(){
cout << "加入牛奶!" << endl;
}
};

//制作茶水
class Tea : public AbstractDrinking{
public:
//烧水
virtual void Boil(){
cout << "煮自来水!" << endl;
}
//冲泡
virtual void Brew(){
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "将茶水倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething(){
cout << "加入食盐!" << endl;
}
};

//业务函数
void DoBussiness(AbstractDrinking* drink){
drink->MakeDrink();
delete drink;
}

int main(int argc, char *argv[])
{
DoBussiness(new Coffee);
cout << "--------------" << endl;
DoBussiness(new Tea);

return 0 ;
}

16.2.3 虚函数和纯虚函数的区别

虚函数:virtual修饰 有函数体 不会导致父类为抽象类。

纯虚函数:virtual修饰,=0,没有函数体 导致父类为抽象类。子类必须重写父类的所有纯虚函数。

16.3 虚析构函数

16.3.1 虚析构函数的定义

virtual修饰析构函数

虚析构:通过父类指针 释放整个子类空间。

class Animal
{
public:
//虚函数
virtual void speak(void)
{
cout << "动物在说话" << endl;
}
//虚析构
virtual ~Animal()
{
cout<<"Animal的析构函数"<<endl;
}
};
class Dog :public Animal
{
public:
//子类重写 父类的虚函数
void speak(void)
{
cout << "狗在汪汪" << endl;
}
~Dog()
{
cout<<"Dog的析构函数"<<endl;
}
};
int main()
{
Animal* p1 = new Dog;
p1->speak();

delete p1;
}

构造的顺序:父类—>成员---->子类

析构的顺序:子类—>成员---->父类

16.4 纯虚析构函数

16.4.1 纯虚析构函数的定义

纯虚析构的本质:是析构函数,各个类的回收工作。而且析构函数不能被继承。

必须为纯虚析构函数提供一个函数体。

纯虚析构函数 必须在类外实现.

#include <iostream>

using namespace std;
class Animal
{
public:
//纯虚函数
virtual void speak(void)= 0 ;

//纯虚析构函数 必须在类外实现
virtual ~Animal()= 0 ;
};
class Dog :public Animal
{
public:
//子类重写 父类的虚函数
void speak(void)
{
cout << "狗在汪汪" << endl;
}
~Dog()
{
cout<<"Dog的析构函数"<<endl;
}
};
Animal::~Animal()
{
cout<<"Animal的析构函数"<<endl;
}

int main(int argc, char* argv[])
{
Animal* p1 = new Dog;
p1->speak();

delete p1;
return 0 ;
}

16.4.2 虚析构和纯虚析构的区别?

虚析构:virtual修饰,有函数体,不会导致父类为抽象类。

纯虚析构:virtual修饰,=0,函数体必须类外实现,导致父类为抽象类。

第十七章 模板

17.1 模板的概述

c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形

参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数

都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实

参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。 c++提供两种模板机制:函数模板和类模

板 类属 - 类型参数化,又称参数模板

总结:

c++面向对象编程思想:封装、继承、多态

c++泛型编程思想:模板

模板的分类:函数模板、类模板

将功能相同,类型不同的函数(类)的类型抽象成虚拟的类型。当调用函数(类实例化对象)的时

候,编译器自动将虚拟的类型 具体化。这个就是函数模板(类模板)。

17.2 函数模板

17.2.1 函数模板的定义方式

模板关键字template

//T只能对当前函数有效

template<typename T>
void swapAll(T &a, T &b)
{
T tmp = a;
a = b;
b=tmp;
return;
}
int main()
{
int a = 10 , b = 20 ;
//函数调用时 根据实参的类型 会自动推导T的类型
swapAll(a, b);
cout<<a<<" "<<b<<endl;

char a1 = 'a', b1 = 'b';
swapAll(a1, b1);
cout<<a1<<" "<<b1<<endl;
}

函数模板 会编译两次:

第一次:是对函数模板 本身编译

第二次:函数调用处 将T的类型具体化

函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。

17.2.2 函数模板的注意点

1 、函数模板 和 普通函数 都识别。(优先选择 普通函数)

//T只能对当前函数有效 typename可以换成class
template<typename T>
void swapAll(T &a, T &b)
{
T tmp = a;
a = b;
b=tmp;
cout<<"函数模板"<<endl;
return;
}
void swapAll(int &a, int &b)
{
int tmp = a;
a = b;
b=tmp;
cout<<"普通函数"<<endl;
return;
}
int main()
{
int a = 10 , b = 20 ;
swapAll(a, b);//调用普通函数
cout<<a<<" "<<b<<endl;
}

2 、函数模板 和 普通函数 都识别。强制使用函数模板

int main()
{
int a = 10 , b = 20 ;
//强制使用函数模板
swapAll<>(a, b);//调用函数模板
swapAll<int>(a, b);
cout<<a<<" "<<b<<endl;
}

3 、函数模板 自动类型推导时 不能对函数的参数 进行 自动类型转换。

template<typename T>
void myPrintAll(T a, T b)
{
cout<<a<<" "<<b<<endl;
cout<<"函数模板"<<endl;
}

void myPrintAll(int a, int b)
{
cout<<a<<" "<<b<<endl;
cout<<"普通函数"<<endl;
}
int main()
{
myPrintAll( 10 , 20 );//普通函数
myPrintAll('a','b');//函数模板
myPrintAll( 10 ,'b');//普通函数
//强制说明T为int类型 就支持自动类型转换
myPrintAll<int>( 10 ,'b');//函数模板
}

17.2.3 函数模板的重载

template<typename T>
void myPrintAll(T a)
{
cout<<a<<endl;
cout<<"函数模板"<<endl;
}
template<typename T>
void myPrintAll(T a, T b)
{
cout<<a<<" "<<b<<endl;
cout<<"函数模板"<<endl;
}

17.2.4 函数模板的局限性

当函数模板 推导出 T为数组或其他自定义类型数据 可能导致运算符 不识别。

解决办法一:运算符重载(推荐)

#include <iostream>

using namespace std;
class Data
{
friend ostream& operator<<(ostream& out, Data ob);
private:
int data;
public:
Data(){}
Data(int data)
{
this->data = data;
}
};
ostream& operator<<(ostream& out, Data ob)
{
out<<ob.data;
return out;
}

template<typename T>
void myPrintAll(T a)
{
cout<<a<<endl;
cout<<"函数模板"<<endl;
}
int main(int argc, char *argv[])
{
myPrintAll( 10 );//10
myPrintAll('a');//'a'
Data ob1( 100 );
myPrintAll(ob1);//100
return 0 ;
}

解决办法二:具体化函数模板

template<typename T>
void myPrintAll(T a)
{
cout<<a<<endl;
cout<<"函数模板"<<endl;
}

class Data
{
friend void myPrintAll<Data>(Data a);
private:
int data;
public:
Data(){}
Data(int data)
{
this->data = data;
}
};

//函数模板具体化
template<> void myPrintAll<Data>(Data a)
{
cout<<a.data<<endl;
cout<<"函数模板"<<endl;
}
int main(int argc, char *argv[])
{
myPrintAll( 10 );
myPrintAll('a');

Data ob1( 100 );
myPrintAll(ob1);

return 0 ;
}

17.3 类模板

17.3.1 类模板基本概念

类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同

的,仅仅是数据类型不同。 类模板用于实现类所需数据的类型参数化。

17.3.2 类模板的定义方式

类模板 实例化对象 不能自动类型推导(重要)。

//类模板
template<class T1, class T2>
class Data
{
private:
T1 a;
T2 b;
public:
Data(){}
Data(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
void showData()
{
cout<<a<<" "<<b<<endl;
}
};

17.3.3 类模板的成员函数在类外实现

template<class T1, class T2>
class Data
{
private:
T1 a;
T2 b;
public:
Data(){}
Data(T1 a, T2 b);
void showData();
};
template<class T1, class T2>
Data<T1,T2>::Data(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
template<class T1, class T2>
void Data<T1,T2>::showData()
{
cout<<a<<" "<<b<<endl;
}

//类模板

17.3.4 函数模板作为类模板的友元

//类模板

template<class T1, class T2>
class Data
{
template<typename T3, typename T4>
friend void myPrintData(Data<T3, T4> &ob);
private:
T1 a;
T2 b;
public:
Data(){}
Data(T1 a, T2 b);
void showData();
};

//函数模板
template<typename T3, typename T4> void myPrintData(Data<T3, T4> &ob)
{
cout<<ob.a<<" "<<ob.b<<endl;
}
int main()
{
Data<int, char> ob1( 100 , 'A');
myPrintData(ob1);
}

17.3.5 普通函数作为类模板的友元

//类模板
template<class T1, class T2>
class Data
{
friend void myPrintData(Data<int, char> &ob);
private:
T1 a;
T2 b;
public:

Data(){}
Data(T1 a, T2 b);
void showData();
};

void myPrintData(Data<int, char> &ob)
{
cout<<ob.a<<" "<<ob.b<<endl;
}
int main()
{
Data<int, char> ob1( 100 , 'A');
myPrintData(ob1);
}

17.3.6 模板头文件 和源文件分离问题

data.hpp

#ifndef DATA_H
#define DATA_H
#include<iostream>
using namespace std;
template<class T1, class T2>
class Data
{
private:
T1 a;
T2 b;
public:
Data();
Data(T1 a, T2 b);
void showData(void);
};

template<class T1, class T2>
Data<T1, T2>::Data()
{
cout<<"无参构造"<<endl;
}
template<class T1, class T2>
Data<T1, T2>::Data(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
template<class T1, class T2>
void Data<T1, T2>::showData(void)
{
cout<<a<<" "<<b<<endl;
}
#endif // DATA_H

main.cpp

#include <iostream>
#include"data.hpp"
using namespace std;
int main(int argc, char *argv[])
{
Data<int,char> ob1( 100 ,'A');
ob1.showData();
return 0 ;
}

17.3.7 案例:设计数组类模板

数组类模板:可以存放任意数据类型

myarray.hpp

#ifndef MYARRAY_HPP
#define MYARRAY_HPP
#include<iostream>
#include<string.h>
#include<string>
using namespace std;
template<class T>
class MyArray
{
template<typename T1>
friend ostream& operator<<(ostream& out, MyArray<T1> ob);
private:
T *arr;//保存数组首元素地址
int size;//大小
int capacity;//容量
public:
MyArray();
MyArray(int capacity);
MyArray(const MyArray &ob);
~MyArray();
MyArray& operator=(MyArray &ob);

void pushBack(T elem);
void sortArray();
};

#endif // MYARRAY_HPP

template<class T>
MyArray<T>::MyArray()
{
capacity = 0 ;
size = 0 ;
arr = NULL;
}

template<class T>
MyArray<T>::MyArray(int capacity)
{
this->capacity = capacity;


size = 0 ;
arr = new T[this->capacity];
memset(arr, 0 ,sizeof(T)*capacity);
}

template<class T>
MyArray<T>::MyArray(const MyArray &ob)
{
if(ob.arr == NULL)
{
arr = NULL;
size = 0 ;
capacity= 0 ;
}
else
{
capacity = ob.capacity;
size = ob.size;
arr = new T[capacity];
memcpy(arr, ob.arr, sizeof(T)*capacity);
}

}

template<class T>
MyArray<T>::~MyArray()
{
if(arr != NULL)
{
delete [] arr;
}
}

template<class T>
MyArray<T> &MyArray<T>::operator=(MyArray<T> &ob)
{
//判断this->arr是否存在空间
if(arr != NULL)
{
delete [] arr;
arr=NULL;
}

size = ob.size;
capacity = ob.capacity;
arr = new T[capacity];
memset(arr, 0 ,sizeof(T)*capacity);
memcpy(arr, ob.arr, sizeof(T)*capacity);
return *this;
}

template<class T>
void MyArray<T>::pushBack(T elem)
{
if(size==capacity)//满
{
//扩展容量
capacity = (capacity== 0? 1 : 2 *capacity) ;


//申请空间

T *tmp = new T[capacity];

if(arr != NULL)
{
//将旧空间的内容拷贝到新空间
memcpy(tmp,arr,sizeof(T)*size);
//释放旧空间
delete [] arr;
}

arr = tmp;
}

arr[size] = elem;
size++;
return;
}

template<class T>
void MyArray<T>::sortArray()
{
if(arr == NULL)
{
cout<<"容器为空间"<<endl;
}
else
{
int i= 0 ,j= 0 ;
for(i= 0 ;i<size- 1 ;i++)
{
for(j= 0 ;j<size-i- 1 ;j++)
{
if(arr[j] > arr[j+ 1 ])
{
T tmp = arr[j];
arr[j] = arr[j+ 1 ];
arr[j+ 1 ] = tmp;
}
}
}
}
return;
}
template<typename T1>
ostream& operator<<(ostream& out, MyArray<T1> ob)
{
int i= 0 ;
for(i= 0 ;i<ob.size;i++)
{
out<<ob.arr[i]<<" ";

}

return out;
}

main.cpp

#include <iostream>
#include "myarray.hpp"
using namespace std;
class Person
{
friend ostream& operator<<(ostream& out, Person ob);
private:
int num;
string name;
float score;
public:
Person(){}
Person(int num,string name, float score)
{
this->num = num;
this->name = name;
this->score = score;
}
bool operator>(const Person &ob)
{
return num>ob.num;
}
};
ostream& operator<<(ostream& out, Person ob)
{
int i= 0 ;

out<<ob.num<<" "<<ob.name<<" "<<ob.score<<endl;
return out;
}

int main(int argc, char *argv[])
{
MyArray<int> ob1( 5 );
ob1.pushBack( 10 );
ob1.pushBack( 30 );
ob1.pushBack( 20 );
ob1.pushBack( 50 );
ob1.pushBack( 40 );

cout<<ob1<<endl;
ob1.sortArray();
cout<<ob1<<endl;

MyArray<char> ob2( 5 );
ob2.pushBack('A');
ob2.pushBack('C');
ob2.pushBack('D');
ob2.pushBack('B');
ob2.pushBack('F');

cout<<ob2<<endl;
ob2.sortArray();
cout<<ob2<<endl;

MyArray<Person> ob3;
ob3.pushBack(Person( 100 ,"lucy", 88.8f));
ob3.pushBack(Person( 103 ,"bob", 89.9f));
ob3.pushBack(Person( 105 ,"tom", 98.8f));
ob3.pushBack(Person( 102 ,"德玛", 99.8f));

cout<<ob3<<endl;
ob3.sortArray();
cout<<ob3<<endl;
return 0 ;
}

17.4 类模板的派生

17.4.1 类模板 派生出 普通类

#include <iostream>

using namespace std;
//类模板
template<class T1, class T2>
class Base
{
private:
T1 a;
T2 b;
public:
Base(){}
Base(T1 a, T2 b);
void showData();
};
template<class T1, class T2>
Base<T1,T2>::Base(T1 a, T2 b)
{
this->a = a;
this->b = b;
}

template<class T1, class T2>
void Base<T1,T2>::showData()
{
cout<<a<<" "<<b<<endl;
}

//类模板派生处普通类
class Son1:public Base<int,char>
{
public:
int c;
public:
Son1(int a, char b, int c):Base<int,char>(a, b){
this->c = c;
}
};
int main(int argc, char *argv[])
{

Son1 ob1( 100 , 'A', 200 );
ob1.showData();
cout<<ob1.c<<endl;
return 0 ;
}

17.4.2 类模板 派生处 类模板

#include <iostream>
using namespace std;
//类模板
template<class T1, class T2>
class Base
{
private:
T1 a;
T2 b;
public:
Base(){}
Base(T1 a, T2 b);
void showData();
};
template<class T1, class T2>
Base<T1,T2>::Base(T1 a, T2 b)
{
this->a = a;
this->b = b;
}

template<class T1, class T2>
void Base<T1,T2>::showData()
{
cout<<a<<" "<<b<<endl;
}

//类模板派生处类模板
template<class T1, class T2, class T3>
class Son1:public Base<T1,T2>
{
public:
T3 c;
public:
Son1(T1 a, T2 b, T3 c):Base<T1,T2>(a, b){
this->c = c;
}
};
int main(int argc, char *argv[])
{
Son1<int,char, int> ob1( 100 , 'A', 200 );
ob1.showData();
cout<<ob1.c<<endl;
return 0 ;
}

第十八章 类型转换

标准c++提供了一个显示的转换的语法,来替代旧的C风格的类型转换。 使用C风格的强制转换可以把想

要的任何东西转换成我们需要的类型。那为什么还需要一个新的C++类型的强制转换呢? 新类型的强制

转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。C++风格的强制转换其他

的好处是,它们能更清晰的表明它们要干什么。程序员只要扫一眼这样的代码,就能立即知道一个强制

转换的目的

18.1 上行、下行转换的概述

18.2 static_cast静态类型转换

class Base{};
class Son:public Base{};
class Other{};

用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
基本类型:支持

int num = static_cast<int>(3.14);//ok

上行转换:支持 安全

Base *p = static_cast<Base *>(new Son);

下行转换:支持 (不安全)

Son *p2 = static_cast<Son *>(new Base);

不相关类型转换:不支持

Base *p3 = static_cast<Base *>(new Other);//err

18.3 dynamic_cast静态类型转换

dynamiccast主要用于类层次间的上行转换和下行转换

基本类型:不支持

int num = dynamic_cast<int>(3.14);//err

上行转换:支持

Base *p1 = dynamic_cast<Base *>(new Son);//ok

下行转换:不支持(不安全)

Son *p2 = dynamic_cast<Son *>(new Base);//err

不相关类型转换:不支持

Base *p3 = dynamic_cast<Base *>(new Other);//err

18.4 const_cast常量转换

1 、将const修饰的指针或引用 转换成 非const (支持)

const int *p1;
int *p2 = const_cast<int *>(p1);
const int &ob = 10 ;
int &ob1 = const_cast<int &>(ob);

2 、将非const修饰的指针或引用 转换成 const (支持)

int *p3;
const int *p4 = const_cast<const int *>(p3);
int data = 10 ;
const int &ob2 = const_cast<const int &>(data);

18.5 重新解释转换(reinterpret_cast) (最不安全)

第十九章 异常

19.1 异常的基本概念

遇到错误 抛出异常 捕获异常

异常:是指在程序运行的过程中发生的一些异常事件(如:除 0 溢出,数组下标越界,所要读取的文件不

存在,空指针,内存不足,访问非法内存等等)。(异常是一个类)

c++异常机制相比C语言异常处理的优势?

函数的返回值可以忽略,但异常不可忽略。(忽略异常 程序结束)

整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。

19.2 异常的抛出和捕获

19.2.1 异常的抛出和捕获

try
{
throw 异常值;
}
catch(异常类型 1 异常值 1 )
{
处理异常的代码 1 ;
}
catch(异常类型 2 异常值 2 )
{
处理异常的代码 2 ;
}
catch(...)//任何异常都捕获
{
处理异常的代码 3 ;
}

19.2.2 异常抛出和捕获案例

int ret = 0 ;
try
{
//throw 1;
//throw 'A';
throw 2.14f;

}
catch(int e)//捕获
{
cout<<"int异常值为:"<<e<<endl;
}
catch(char e)//捕获
{
cout<<"char异常值为:"<<e<<endl;
}
catch(...)//捕获所有异常
{
cout<<"其他异常值为:"<<endl;
}

19.3 栈解旋

异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析

构的顺序与构造的顺序相反,这一过程称为栈的解旋.

try
{
Data ob1;
Data ob2;
Data ob3;
throw 1 ;//抛出异常后 ob3 ob2 ob1依次自动释放(栈解旋)
}

19.3.1 栈解旋案例

#include <iostream>

using namespace std;

class Data
{
public:
int a;
public:
Data(){}
Data(int a)
{
this->a = a;
cout<<"构造函数"<<a<<endl;
}
~Data()
{
cout<<"析构函数"<<a<<endl;
}
};

int main()
{
int ret = 0 ;
try
{
Data ob1( 10 );
Data ob2( 20 );
Data ob3( 30 );
throw 1 ;
}

catch(int)//捕获
{
cout<<"int异常值为:"<<endl;
}
catch(char)//捕获
{
cout<<"char异常值为:"<<endl;
}
catch(...)//捕获

cout<<"其他异常值为:"<<endl;
}
}

19.4 异常的接口声明

异常的接口声明:描述的是 可以抛出哪些类型的异常

19.4.1 函数默认 可以抛出任何类型的异常(推荐)

void fun01()
{
//throw 1;
//throw '1';
throw "hello";
}

19.4.2 只能抛出特定类型异常

void fun02() throw(int,char)
{
//throw 1;
//throw '1';
throw "hello";//抛出 不能捕获
}

19.4.3 不能抛出任何异常

{

void fun03() throw()
{
throw 1 ;
//throw '1';
//throw "hello";//抛出 不能捕获
}

19.5 异常变量的生命周期

class MyException
{
public:
MyException(){
cout << "异常变量构造" << endl;
};
MyException(const MyException & e)
{
cout << "拷贝构造" << endl;
}
~MyException()
{
cout << "异常变量析构" << endl;
}
};

19.5.1 以普通对象 接异常值

19.5.2 以对象指针 接异常值

19.5.3 对象引用 接异常值(推荐)

19.6 异常的多态

//异常基类
class BaseException
{
public:
virtual void printError(){};
};

// 空指针异常
class NullPointerException : public BaseException
{
public:
virtual void printError()
{
cout << "空指针异常!" << endl;
}
};
// 越界异常
class OutOfRangeException : public BaseException
{
public:
virtual void printError()
{
cout << "越界异常!" << endl;
}
};

void doWork()
{

// throw NullPointerException();
throw OutOfRangeException();
}

int main()
{
try
{
doWork();
}
catch (BaseException &ex) // 父类引用 可以捕获搭配该父类派生出的所有子类的子类
{
ex.printError();
}
}

19.7 C++标准异常

异常名称 描述

exception 所有标准异常类的父类

bad_alloc 当operator new and operator new[],请求分配内存失败时

bad_exception

这是个特殊的异常,如果函数的异常抛出列表里声明了bad exception 异常,

当函数内部抛出了异常抛出列表中没有的异常,这是调用的 unexpected 函数

中若抛出异常,不论什么类型,都会被替换为 bad exception类型

bad_typeid

使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时

抛出bad_typeid异常

bad_cast 使用dynamic_cast转换引用失败的时候

ios_base::failure io操作过程出现错误

logic_error 逻辑错误,可以在运行前检测的错误

runtime_error 运行时错误,仅在运行时才可以检测的错误

异常名称 描述

length_error 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作

domain_error

参数的值域错误,主要用在数学函数中。例如使用一个负值调用只能操作非

负数的函数

out of range 超出有效范围

invalid_argument

参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字

符不是’0’或’1’的时候,抛出该异常

logic_error的子类

异常名称 描述

range_error 计算结果超出了有意义的值域范围

overflow_error 算术计算上溢

underflow_error 算术计算下溢

invalid_argument

参数不合适。在标准库中,当利用string对象构造bitset时,而string中的字

符不是’0’或’1’的时候,抛出该异常

runtime_error的子类

19.8 编写自己的异常

基于标准异常基类 编写自己的异常类。

class NewException : public exception
{
private:
string msg;

public:
NewException() {}
NewException(string msg)
{
this->msg = msg;
}
// 重写父类的what
virtual const char *what() const throw() // 防止父类在子类前抛出标准异常
{
// 将string类转换成char *
return this->msg.c_str();
}
~NewException() {}
} int main()
{
try
{
throw NewException("哈哈,自己的异常");
}
catch (exception &e)
{

cout << e.what() << endl;
}
}

第二十章 STL之容器

20.1 STL 概述

长久以来,软件界一直希望建立一种可重复利用的东西,以及一种得以制造出”可重复运用的东西”的方

法,让程序员的心血不止于随时间的迁移,人事异动而烟消云散,从函数(functions),类别(classes),函数

库(function libraries),类别库(classlibraries)、各种组件,从模块化设计,到面向对象(object oriented

),为的就是复用性的提升。

复用性必须建立在某种标准之上。但是在许多环境下,就连软件开发最基本的数据结构(data

structures) 和算法(algorithm)都未能有一套标准。大量程序员被迫从事大量重复的工作,竟然是为了完

成前人已经完成而自己手上并未拥有的程序代码,这不仅是人力资源的浪费,也是挫折与痛苦的来源。

为了建立数据结构和算法的一套标准,并且降低他们之间的耦合关系,以提升各自的独立性、弹性、

交互操作性(相互合作性,interoperability),诞生了 STL

20.1.1 STL的基本概念

STL(Standard Template Library,标准模板库),是惠普实验室开发的一系列软件的统 称。现在主要出现

在 c++中,但是在引入 c++之前该技术已经存在很长时间了。 STL 从广义上分为: 容器(container) 算法

(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL 几乎所有的代码都采用了模

板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。STL(Standard

Template Library)标准模板库,在我们 c++标准程序库中隶属于 STL的占到了 80%以上。

20.1.2 STL的六大组件

这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。

1 、容器:存放数据

2 、算法:操作数据

3 、迭代器:算法 通过迭代器 操作容器数据

4 、仿函数:为算法提供更多的策略

5 、适配器:为算法提供更多参数的接口

6 、空间配置器:为算法和容器 动态分配、管理空间

STL 的一个重要特性是将数据和操作分离。数据由容器类别加以管理,操作则由 特定的算法完成。

算法分为:质变算法和非质变算法。

质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等 等

非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍 历、寻找极值等

20.2 string类

20.2.1 string 容器基本概念

C 风格字符串(以空字符结尾的字符数组)太过复杂难于掌握,不适合大程序的开发,所以 C++标准库定义

了一种 string 类,定义在头件。

String 和 c 风格字符串对比:

Char * 是一个指针,String 是一个类

string 封装了 char * ,管理这个字符串,是一个 char 型的容器。

String 封装了很多实用的成员方法

查找 find,拷贝 copy,删除 delete 替换 replace,插入 insert

不用考虑内存释放和越界

string 管理 char*所分配的内存。每一次 string 的复制,取值都由 string 类负责维护,不用担心复制

越界和取值越界等。

20.2.2 string 容器常用操作

1 、string 构造函数

string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个 string 对象初始化另一个 string 对象
string(const char* s);//使用字符串 s 初始化
string(int n, char c);//使用 n 个字符 c 初始化 v

2 、string 基本赋值操作

string& operator=(const char* s);//char*类型字符串 赋值给当前的字符串
string& operator=(const string &s);//把字符串 s 赋给当前的字符串
string& operator=(char c);//字符赋值给当前的字符串
string& assign(const char *s);//把字符串 s 赋给当前的字符串
string& assign(const char *s, int n);//把字符串 s 的前 n 个字符赋给当前的字符串
string& assign(const string &s);//把字符串 s 赋给当前字符串
string& assign(int n, char c);//用 n 个字符 c 赋给当前字符串
string& assign(const string &s, int start, int n);//将 s 从 start 开始 n 个字符赋值
给字符串

3 、string 存取字符操作

char& operator[](int n);//通过[]方式取字符
char& at(int n);//通过 at 方法获取字符
//[] 越界不会抛出异常 at方法 越界会抛出异常

4 、string 拼接操作

string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char* str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char *s);//把字符串 s 连接到当前字符串结尾
string& append(const char *s, int n);//把字符串 s 的前 n 个字符连接到当前字符串结尾
string& append(const string &s);//同 operator+=()
string& append(const string &s, int pos, int n);//把字符串 s 中从 pos 开始的 n 个字符
连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加 n 个字符 c

5 、string 查找和替换

int find(const string& str, int pos = 0 ) const; //查找 str 第一次出现位置, 从 pos 开
始查找
int find(const char* s, int pos = 0 ) const; //查找 s 第一次出现位置,从 pos开始查找
int find(const char* s, int pos, int n) const; //从 pos 位置查找 s 的前 n 个字符第一
次位置
int find(const char c, int pos = 0 ) const; //查找字符 c 第一次出现位置
int rfind(const string& str, int pos = npos) const;//查找 str 最后一次位置, 从 pos
开始查找
int rfind(const char* s, int pos = npos) const;//查找 s 最后一次出现位置,从pos 开始查int rfind(const char* s, int pos, int n) const;//从 pos 查找 s 的前 n 个字符最后一次
位置
int rfind(const char c, int pos = 0 ) const; //查找字符 c 最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从 pos 开始 n 个字符为字符
串 str
string& replace(int pos, int n, const char* s); //替换从 pos 开始的 n 个字符为字符串
s

6 、string 比较操作

/*
compare 函数在>时返回 1 ,<时返回 -1,==时返回 0 。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的 A 比小写的 a 小。
*/
int compare(const string &s) const;//与字符串 s 比较
int compare(const char *s) const;//与字符串 s 比较
重载了> < ==等关运算符

7 、string 子串

string substr(int pos = 0 , int n = npos) const;//返回由 pos 开始的 n 个字符组成的字符

8 、string 插入和删除操作

string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入 n 个字符 c
string& erase(int pos, int n = npos);//删除从 Pos 开始的 n 个字符

9 、string 和 c-style 字符串转换

//string 转 char*
string str = "itcast";
const char* cstr = str.c_str();
//char* 转 string
char* s = "itcast";
string str(s);

在 c++中存在一个从 const char 到 string 的隐式类型转换,却不存在从一个 string对象到 Cstring 的自

动类型转换。对于 string 类型的字符串,可以通过 cstr()函数返回 string 对象对应的 C_string. 通常,程

序员在整个程序中应坚持使用 string 类对象,直到必须将内容转化为 char 时才将其转换为 C_string. 提

示: 为了修改 string 字符串的内容,下标操作符[]和 at 都会返回字符的引用。但当字符串的内存被重新

分配之后,可能发生错误.

string s = "abcdefg";
char& a = s[ 2 ];
char& b = s[ 3 ];
a = '1';
b = '2';
cout << s << endl;
cout << (int*)s.c_str() << endl;
s = "----------------------------";
//a = '1';
//b = '2';
cout << s << endl;
cout << (int*)s.c_str() << endl;

20.3 vector容器

20.3.1 vector容器的概述

vector 的数据安排以及操作方式,与 array 非常相似,两者的唯一差别在于空间的运用的灵活性。

Array 是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己

来,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。Vector 是动态空

间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此 vector 的运用对于内存的合理

利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的 array

了。 Vector 的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦 vector 旧空

间满了,如果客户每新增一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充

空间(不论多大),一如刚所说,是”配置新空间-数据移动-释放旧空间”的大工程,时间成本很高,应该加入

某种未雨绸缪的考虑,稍后我们便可以看到 vector 的空间配置策略。

v.begin():获取容器的起始迭代器(指向第 0 个元素)

v.end():获取容器的结束迭代器(指向最后一个元素的下一个位置)

20.3.2 vector 的数据结构

Vector 所采用的数据结构非常简单,线性连续空间,它以两个迭代器 Myfirst 和Mylast 分别指向配置得

来的连续空间中目前已被使用的范围,并以迭代器_Myend指向整块连续内存空间的尾端。 为了降低空

间配置时的速度成本,vector 实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这边是

容量的概念。换句话说,一个 vector 的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下

次再有新增元素,整个 vector 容器就得另觅居所。

注意: 所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空

间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。因此,对 vector 的任何操

作,一旦引起空间的重新配置,指向原 vector 的所有迭代器就都失效了。这是程序员容易犯的一个错

误,务必小心

20.3.3 vector 常用 API 操作

1 、vector 构造函数

vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将 v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将 n 个 elem 拷贝给本身。
vector(const vector &vec);//拷贝构造函数。

2 、vector 常用赋值操作

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将 vec 与本身的元素互换。

3 、vector 大小操作

size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,
则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。如果
容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留 len 个元素长度,预留位置不初始化,元素不可访问。

4 、vector 数据存取操作

at(int idx); //返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range 异常。
operator[];//返回索引 idx 所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素

5 、vector 插入和删除操作

insert(const_iterator pos, int count,ele);//迭代器指向位置 pos 插入 count个元素 ele.
push_back(ele); //尾部插入元素 ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从 start 到 end 之间的元erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素

6 、巧用 swap 收缩内存空间

vector<int> v1;
v1.reserve( 1000 );
v1.push_back( 10 );
v1.push_back( 20 );
v1.push_back( 30 );
v1.push_back( 40 );
cout<<"容量:"<<v1.capacity()<<", 大小:"<<v1.size()<<endl;//1000 4
//resize只能修改大小 不能修改容量
//v1.resize(4);
vector<int>(v1).swap(v1);
cout<<"容量:"<<v1.capacity()<<", 大小:"<<v1.size()<<endl;//4 4

7 、reserve 预留空间

#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> v;
v.reserve( 100000 );
int* pStart = NULL;
int count = 0 ;
for (int i = 0 ; i < 100000 ;i ++){
v.push_back(i);
if (pStart != &v[ 0 ]){
pStart = &v[ 0 ];
count++;
}
}
cout << "count:" << count << endl;
return 0 ;
}

20.4 deque 容器

20.4.1 deque 容器基本概念

Vector 容器是单向开口的连续内存空间,deque 则是一种双向开口的连续线性空间。所谓的双向开口,

意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector 容器也可以在头尾两端插入元素,

但是在其头部操作效率奇差,无法被接受

Deque 容器和 vector 容器最大的差异,一在于 deque 允许使用常数项时间对头端进行元素的插入和删

除操作。二在于 deque 没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段

新的空间并链接起来,换句话说,像vector 那样,”旧空间不足而重新配置一块更大空间,然后复制元

素,再释放旧空间”这样的事情在 deque 身上是不会发生的。也因此,deque 没有必须要提供所谓的空

间保留(reserve)功能. 虽然 deque 容器也提供了 Random Access Iterator,但是它的迭代器并不是普通

的指针,其复杂度和 vector 不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该

尽可能的使用 vector,而不是deque。对 deque 进行的排序操作,为了最高效率,可将 deque 先完整

的复制到一个 vector 中,对 vector 容器进行排序,再复制回 deque

20.4.2 deque 容器实现原理

Deque 容器是连续的空间,至少逻辑上看来如此,连续现行空间总是令我们联想到array 和

vector,array 无法成长,vector 虽可成长却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申

请更大空间 (2)原数据复制新空间 (3)释放原空间三步骤,如果不是 vector 每次配置新的空间时都留有余

裕,其成长假象所带来的代价是非常昂贵的。 Deque 是由一段一段的定量的连续空间构成。一旦有必要

在deque 前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque 的头端或者尾端。

//预先开辟空间

Deque 最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了

重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。 既然 deque 是分段连续内存空间,

那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。

Deque 代码的实现远比 vector 或 list 都多得多。 Deque 采取一块所谓的map(注意,不是 STL 的 map

容器)作为主控,这里所谓的 map 是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一

个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque 的存储空间的主体。

20.4.3 deque 常用 API

1 、deque 构造函数

deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将 n 个 elem 拷贝给本身。
deque(const deque &deq);//拷贝构造函数。

2 、deque 赋值操作

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将 deq 与本身的元素互换

3 、deque 大小操作

deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变
短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置,如
果容器变短,则末尾超出容器长度的元素被删除。

4 、deque 双端插入和删除操作

push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据

5 、deque 数据存取

at(idx);//返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range。
operator[];//返回索引 idx 所指的数据,如果 idx 越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据

6 、deque 插入操作

insert(pos,elem);//在 pos 位置插入一个 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。

7 、deque 删除操作

clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除 pos 位置的数据,返回下一个数据的位置。

8 、案例(作业)

有 5 名选手:选手 ABCDE, 10 个评委分别对每一名选手打分,去除最高分,去除评委中最低分,取平

均分。

  1. 创建五名选手,放到 vector 中

  2. 遍历 vector 容器,取出来每一个选手,执行 for 循环,可以把 10 个评分打分存到 deque 容器中

  3. sort 算法对 deque 容器中分数排序,pop_back pop_front 去除最高和最低分

  4. deque 容器遍历一遍,累加分数,累加分数/d.size()

  5. person.score = 平均分

20.5 stack容器

20.5.1 stack 容器基本概念

stack 是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,形式如图所示。stack 容器

允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取 stack 的其

他元素。换言之,stack 不允许有遍历行为。 有元素入栈的操作称为:push,将元素出 stack 的操作称为

pop.

Stack 所有元素的进出都必须符合”先进后出”的条件,只有 stack 顶端的元素,才有机会被外界取用。

Stack 不提供遍历功能,也不提供迭代器。

20.5.2 stack常用API

1 、stack 构造函数

stack<T> stkT;//stack 采用模板类实现, stack 对象的默认构造形式:
stack(const stack &stk);//拷贝构造函数

2 、stack 赋值操作

stack& operator=(const stack &stk);//重载等号操作符

3 、stack 数据存取操作

push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素

4 、stack 大小操作

empty();//判断堆栈是否为空
size();//返回堆栈的大小

20.6 queue 容器

20.6.1 queue 容器基本概念

Queue 是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新

增元素,从另一端移除元素。

Queue 所有元素的进出都必须符合”先进先出”的条件,只有 queue 的顶端元素,才有机会被外界取用。

Queue 不提供遍历功能,也不提供迭代器。

20.6.2 queue常用API

queue<T> queT;//queue 采用模板类实现,queue 对象的默认构造形式:
queue(const queue &que);//拷贝构造函数
push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素
queue& operator=(const queue &que);//重载等号操作符
empty();//判断队列是否为空
size();//返回队列的大小

20.7 list容器

20.7.1 list 容器基本概念

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接

次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每

个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相较于

vector 的连续线性空间,list 就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者

释放一个元素的空间。因此,list 对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的

元素插入或元素的移除,list 永远是常数时间。 List 和vector 是两个最常被使用的容器。 List 容器是一

个双向链表。

采用动态存储分配,不会造成内存浪费和溢出 链表执行插入和删除操作十分方便,修改指针即可,不需

要移动大量元素 链表灵活,但是空间和时间额外耗费较大。

20.7.2 list常用API

1 、list 构造函数

list<T> lstT;//list 采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将 n 个 elem 拷贝给本身。
list(const list &lst);//拷贝构造函数。

2 、 list 数据元素插入和删除操作

push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在 pos 位置插 elem 元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
insert(pos,beg,end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除 pos 位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与 elem 值匹配的元素。

3 、list 大小操作

size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,则末
尾超出容器长度的元素被删除。
resize(num, elem);//重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。如果容器
变短,则末尾超出容器长度的元素被删除。

4 、list 赋值操作

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将 lst 与本身的元素互换。

5 、list 数据的存取

front();//返回第一个元素。
back();//返回最后一个元素。

6 、list 反转排序

reverse();//反转链表,比如 lst 包含 1,3,5 元素,运行此方法后,lst 就包含 5,3,1元素。
sort(); //list 排序

20.8 set/multiset容器

20.8.1 set/multiset 容器基本概念

set 的特性是。所有元素都会根据元素的键值自动被排序,set 的元素即是键值又是实值。

set 不允许两个元素有相同的键值。

set容器的迭代器是只读迭代器,不允许修改键值,会破坏set的内存布局。

multiset 特性及用法和 set 完全相同,唯一的差别在于它允许键值重复。

20.8.2 set常用API

1 、set 构造函数

set<T> st;//set 默认构造函数:
mulitset<T> mst; //multiset 默认构造函数:
set(const set &st);//拷贝构造函数

2 、set 赋值操作

set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器

3 、set 大小操作

size();//返回容器中元素的数目
empty();//判断容器是否为空

4 、set 插入和删除操作

insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为 elem 的元素。

5 、set 查找操作

find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回 set.end();
count(key);//查找键 key 的元素个数
lower_bound(keyElem);//返回第一个 key>=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器

20.8.3 对组(pair)

对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用 pair 的两个公有属性 first 和 second 访问。 类模板:template <class T1, class T2> struct pair. 如何创建对组?

//第一种方法创建一个对组
pair<string, int> pair1(string("name"), 20 );
cout << pair1.first << endl; //访问 pair 第一个值
cout << pair1.second << endl;//访问 pair 第二个值
//第二种
pair<string, int> pair2 = make_pair("name", 30 );
cout << pair2.first << endl;
cout << pair2.second << endl;
//pair=赋值
pair<string, int> pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;

20.9 map/multimap 容器

20.9.1 map/multimap 基本概念

Map 的特性是,所有元素都会根据元素的键值自动排序。Map 所有的元素都是pair,同时拥有实值和键

值,pair 的第一元素被视为键值,第二元素被视为实值,map 不允许两个元素有相同的键值。 我们可

以通过 map 的迭代器改变 map 的键值吗?答案是不行,因为 map 的键值关系到 map 元素的排列规

则,任意改变 map键值将会严重破坏 map 组织。如果想要修改元素的实值,那么是可以的。 Map 和

list 拥有相同的某些性质,当对它的容器元素进行新增操作或者删除操作时,操作之前的所有迭代器,在

操作完成之后依然有效,当然被删除的那个元素的迭代器必然是个例外。 Multimap 和 map 的操作类

似,唯一区别 multimap 键值可重复。Map 和 multimap 都是以红黑树为底层实现机制。

map容器:每个元素都是 键值-实值 成对存储,自动根据键值排序, 键值不能重复,不能修改。

20.9.2 map/multimap 常用 API

1 、map 构造函数

map<T1, T2> mapTT;//map 默认构造函数:
map(const map &mp);//拷贝构造函数

2 、map 赋值操作

map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器

3 、 map 大小操作

size();//返回容器中元素的数目
empty();//判断容器是否为空

4 、 map 插入数据元素操作

map.insert(...); //往容器插入元素,返回 pair<iterator,bool>
map<int, string> mapStu;
// 第一种 通过 pair 的方式插入对象
mapStu.insert(pair<int, string>( 3 , "小张"));
// 第二种 通过 pair 的方式插入对象
mapStu.inset(make_pair(- 1 , "校长"));
// 第三种 通过 value_type 的方式插入对象
mapStu.insert(map<int, string>::value_type( 1 , "小李"));
// 第四种 通过数组的方式插入值
mapStu[ 3 ] = "小刘";
mapStu[ 5 ] = "小王";

5 、 map 删除操作

clear();//删除所有元素
erase(pos);//删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中 key 为 keyElem 的对组。

6 、map 查找操作

find(key);//查找键 key 是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回 map.end();
count(keyElem);//返回容器中 key 为 keyElem 的对组个数。对 map 来说,要么是 0 ,要么是 1 。
对 multimap 来说,值可能大于 1lower_bound(keyElem);//返回第一个 key>=keyElem 元素的迭代器。
upper_bound(keyElem);//返回第一个 key>keyElem 元素的迭代器。
equal_range(keyElem);//返回容器中 key 与 keyElem 相等的上下限的两个迭代器

20.9.3 multimap案例

公司今天招聘了 5 个员工, 5 名员工进入公司之后,需要指派员工在那个部门工作人员信息有: 姓名 年

龄 电话 工资等组成 通过 Multimap 进行信息的插入 保存显示 分部门显示员工信息 显示全部员工信息

#define SALE_DEPATMENT 1 //销售部门
#define DEVELOP_DEPATMENT 2 //研发部门
#define FINACIAL_DEPATMENT 3 //财务部门
#define ALL_DEPATMENT 4 //所有部门

20.10 STL 容器使用时机

vector 的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上

上次的记录,但却不会去删除记录,因为记录是事实的描述。

deque 的使用场景:比如排队购票系统,对排队者的存储可以采用 deque,支持头端的快速移除,尾端

的快速添加。如果采用 vector,则头端移除时,会移动大量的数据,速度慢。 vector 与 deque 的比

较: 一:vector.at()比 deque.at()效率高,比如 vector.at(0)是固定的,deque 的开始位置 却是不固定

的。 二:如果有大量释放操作的话,vector 花的时间更少,这跟二者的内部实现有关。

deque 支持头部的快速插入与快速移除,这是 deque 的优点。

list 的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插

入。

set 的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。

map 的使用场景:比如按 ID 号存储十万个用户,想要快速要通过 ID 查找对应的用户。二叉树的查找效

率,这时就体现出来了。如果是

vector 容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

第二十一章 STL之算法

21.1 函数对象

重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对

象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。

注意:

1.函数对象(仿函数)是一个类,不是一个函数。

2.函数对象(仿函数)重载了”() ”操作符使得它可以像函数一样调用。

分类:

如果函数对象 有一个参数 叫:一元函数对象

如果函数对象 有二个参数 叫:二元函数对象

如果函数对象 有三个参数 叫:多元函数对象

class Print
{
public:
void operator()(char *str)
{
cout<<str<<endl;
}
};
int main()
{
Print ob;
ob("hello world");
Print()("hello world");
}

总结:

1 、函数对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调

用的运行时问题。

2 、函数对象超出普通函数的概念,函数对象可以有自己的状态

3 、函数对象可内联编译,性能好。用函数指针几乎不可能

4 、模版函数对象使函数对象具有通用性,这也是它的优势之一

21.2 谓词

//函数对象(仿函数)

返回值为bool类型的普通函数或仿函数 都叫谓词。

如果谓词 有一个参数 叫:一元谓词

如果谓词 有二个参数 叫:二元谓词

21.2.1 一元谓词

#include<vector>
#include<algorithm>
bool greaterThan30(int value)
{
return value> 30 ;
}
class GreaterThan30
{
public:
bool operator()(int value)
{
return value> 30 ;
}
};

int main()
{
vector<int> v1;
v1.push_back( 10 );
v1.push_back( 30 );
v1.push_back( 50 );
v1.push_back( 70 );
v1.push_back( 90 );

//find_if条件查找
vector<int>::iterator ret;
//普通函数提供策略 函数名
//ret = find_if(v1.begin(), v1.end(), greaterThan30);
//仿函数提供策略 类名称+()
ret = find_if(v1.begin(), v1.end(), GreaterThan30());
if(ret != v1.end())
{
cout<<"寻找的结果:"<<*ret<<endl;
}
}

21.2.2 二元谓词

bool myGreaterInt(int v1, int v2)
{
return v1>v2;
}
class MyGreaterInt
{
public:
bool operator()(int v1, int v2)
{
return v1>v2;
}
};
int main()
{
vector<int> v1;
v1.push_back( 10 );
v1.push_back( 50 );
v1.push_back( 30 );
v1.push_back( 90 );
v1.push_back( 70 );

printVectorAll(v1);
//sort(v1.begin(), v1.end(), myGreaterInt);
sort(v1.begin(), v1.end(), MyGreaterInt());

printVectorAll(v1);
}

21.3 内建函数对象

STL 内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。

6 个算数类函数对象,除了 negate 是一元运算,其他都是二元运算。

template<class T> T plus<T>//加法仿函数
template<class T> T minus<T>//减法仿函数
template<class T> T multiplies<T>//乘法仿函数
template<class T> T divides<T>//除法仿函数
template<class T> T modulus<T>//取模仿函数
template<class T> T negate<T>//取反仿函数

6 个关系运算类函数对象,每一种都是二元运算。

template<class T> bool equal_to<T>//等于
template<class T> bool not_equal_to<T>//不等于
template<class T> bool greater<T>//大于
template<class T> bool greater_equal<T>//大于等于
template<class T> bool less<T>//小于
template<class T> bool less_equal<T>//小于等于

逻辑运算类运算函数,not 为一元运算,其余为二元运算。

template<class T> bool logical_and<T>//逻辑与
template<class T> bool logical_or<T>//逻辑或
template<class T> bool logical_not<T>//逻辑非

21.4 适配器

适配器 为算法 提供接口。

21.4.1 函数对象适配器

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//第二步:公共继承binary_function 参数萃取
class printInt:public binary_function<int,int,void>
{
public:
//第三步:整个函数加const修饰
void operator()(int value, int tmp) const
{
cout<<"value="<<value<<" tmp="<< tmp<<endl;
}
};

int main(int argc, char *argv[])
{
vector<int> v1;
v1.push_back( 10 );
v1.push_back( 30 );
v1.push_back( 50 );
v1.push_back( 70 );
v1.push_back( 90 );

//for_each 提取容器的每个元素
//第一步bind2nd 或bind1st
//bind2nd将 100 绑定到第二个参数tmp行 容器的元素在value上
for_each(v1.begin(), v1.end(), bind2nd(printInt(), 100 ) );
cout<<endl;
return 0 ;
}

21.4.2 函数指针适配器 ptr_fun

普通函数名 作为适配器

21.4.3 成员函数 作为适配器 mem_fun_ref

class Data
{
public:
int data;
public:
Data(){}
Data(int d){
data = d;
}
void printInt(int tmp)
{
cout<<"value="<<data+tmp<<endl;
}
};

int main()
{

vector<Data> v1;
v1.push_back(Data( 10 ));
v1.push_back(Data( 30 ));
v1.push_back(Data( 50 ));
v1.push_back(Data( 70 ));
v1.push_back(Data( 90 ));

//for_each 提取容器的每个元素
for_each(v1.begin(), v1.end(), bind2nd(mem_fun_ref(&Data::printInt), 100 ) );
cout<<endl;
}

21.4.4 取反适配器

not1 一元取反

not2 二元取反

int main()
{
vector<int> v1;
v1.push_back( 10 );
v1.push_back( 40 );
v1.push_back( 50 );
v1.push_back( 20 );
v1.push_back( 30 );

//lambda 表达式 c++11才支持
//[]里面啥都不写 lambda不能识别 外部数据
//[=] lambda能对 外部数据 读操作
//[&] lambda能对 外部数据 读写操作
for_each(v1.begin(), v1.end(), [&](int val){
cout<<val<<" ";
} );
cout<<endl;

sort(v1.begin(), v1.end(),not2(greater<int>()));

for_each(v1.begin(), v1.end(), [&](int val){
cout<<val<<" ";
} );
cout<<endl;
}

21.5 算法概述

算法的头文件#include。 是所有 STL 头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍

历,复制,修改,反转,排序,合并等。

21.6 常用遍历算法

21.6.1 for_each 遍历算法

/*
@param beg 开始迭代器
@param end 结束迭代器
@param _callback 函数回调或者函数对象
@return 函数对象
*/
for_each(iterator beg, iterator end, _callback);

21.6.2 transform 算法

遍历算法 遍历容器元素

/*
transform 算法 将指定容器区间元素搬运到另一容器中
注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
@param beg1 源容器开始迭代器
@param end1 源容器结束迭代器
@param beg2 目标容器开始迭代器
@param _cakkback 回调函数或者函数对象
@return 返回目标容器迭代器
*/
transform(iterator beg1, iterator end1, iterator beg2, _callback);

21.7 常用查找算法

21.7.1 find 算法

/*
find 算法 查找元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return 返回查找元素的位置
*/
find(iterator beg, iterator end, value);

21.7.2 find_if 算法

/*
find_if 算法 条件查找
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return bool 查找返回 true 否则 false
*/
find_if(iterator beg, iterator end, _callback);

21.7.3 adjacent_find 算法

/*
adjacent_find 算法 查找相邻重复元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param _callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return 返回相邻元素的第一个位置的迭代器
*/
adjacent_find(iterator beg, iterator end, _callback);

21.7.4 binary_search 算法

/*
binary_search 算法 二分查找法
注意: 在无序序列中不可用
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 查找的元素
@return bool 查找返回 true 否则 false
*/
bool binary_search(iterator beg, iterator end, value);

21.7.5 count 算法

/*
count 算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 回调函数或者谓词(返回 bool 类型的函数对象)
@return int 返回元素个数
*/
count(iterator beg, iterator end, value);

21.7.6 count_if 算法

/*
count_if 算法 统计元素出现次数
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 回调函数或者谓词(返回 bool 类型的函数对象)
@return int 返回元素个数
*/
count_if(iterator beg, iterator end, _callback);

21.8 常用排序算法

21.8.1 merge 算法

/*
merge 算法 容器元素合并,并存储到另一容器中
注意:两个容器必须是有序的
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
*/
merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator
dest);

21.8.2 sort 算法

/*
sort 算法 容器元素排序
@param beg 容器 1 开始迭代器
@param end 容器 1 结束迭代器
@param _callback 回调函数或者谓词(返回 bool 类型的函数对象)
*/
sort(iterator beg, iterator end, _callback)

21.8.3 random_shuffle 算法

/*
random_shuffle 算法 对指定范围内的元素随机调整次序
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/
random_shuffle(iterator beg, iterator end);

21.8.4 reverse 算法

/*
reverse 算法 反转指定范围的元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
*/
reverse(iterator beg, iterator end)

21.9 常用拷贝替换算法

21.9.1 copy 算法

/*
copy 算法 将容器内指定范围的元素拷贝到另一容器中
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param dest 目标起始迭代器
*/
copy(iterator beg, iterator end, iterator dest)

21.9.2 replace 算法

/*
replace 算法 将容器内指定范围的旧元素修改为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param oldvalue 旧元素
@param oldvalue 新元素
*/
replace(iterator beg, iterator end, oldvalue, newvale)

21.9.3 replace_if 算法

/*
replace_if 算法 将容器内指定范围满足条件的元素替换为新元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param callback 函数回调或者谓词(返回 Bool 类型的函数对象)
@param oldvalue 新元素
*/
replace_if(iterator beg, iterator end, _callback, newvalue)

21.9.4 swap 算法

/*
swap 算法 互换两个容器的元素
@param c1 容器 1
@param c2 容器 2
*/
swap(container c1, container c2)

21.10 常用算术生成算法

21.10.1 accumulate 算法

/*
accumulate 算法 计算容器元素累计总和
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value 累加值
*/
accumulate(iterator beg, iterator end, value)

21.10.2 fill 算法

/*
fill 算法 向容器中添加元素
@param beg 容器开始迭代器
@param end 容器结束迭代器
@param value t 填充元素
*/
fill(iterator beg, iterator end, value)

21.11 常用集合算法

21.11.1 set_intersection 算法

/*
set_intersection 算法 求两个 set 集合的交集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2,
iterator dest)

21.11.2 set_union 算法

/*
set_union 算法 求两个 set 集合的并集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator
dest)

21.11.3 set_difference 算法

/*
set_difference 算法 求两个 set 集合的差集
注意:两个集合必须是有序序列
@param beg1 容器 1 开始迭代器
@param end1 容器 1 结束迭代器
@param beg2 容器 2 开始迭代器
@param end2 容器 2 结束迭代器
@param dest 目标容器开始迭代器
@return 目标容器的最后一个元素的迭代器地址
*/
set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2,
iterator dest)

21.12 STL综合案例

举行一场跳水比赛,共有 24 个人参加。比赛共三轮,前两轮为淘汰赛,第三轮为决赛。

比赛方式:分组比赛,每组 6 个人;选手每次要随机分组,进行比赛;

第一轮分为 4 个小组,每组 6 个人。比如编号为: 100-123. 整体进行抽签(draw)后顺序跳水。当小组跳

水完后,淘汰组内排名最后的三个选手,然后继续下一个小组的比赛。

第二轮分为 2 个小组,每组 6 人。比赛完毕,淘汰组内排名最后的三个选手,然后继续下一个小组的比

赛。

第三轮只剩下 1 组 6 个人,本轮为决赛,选出前三名。 比赛评分: 10 个评委打分,去除最低、最高分,

求平均分每个选手跳完由 10 个评委分别打分。该选手的最终得分是去掉一个最高分和一个最低分,求得

剩下的 8 个成绩的平均分。选手的名次按得分降序排列。

选手 ( ABCDEFGHIJKLMNOPQRSTUVWX ) 姓名、得分;选手编号

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值