网站建设和系统集成,wordpress手机编辑器插件,玫瑰在线 网站建设内容,苏州网站seo公司[TOC]
一、 C基础
C的IDE有CLion、Visual Studio、DEV C、eclipse等等#xff0c;这里使用CLion进行学习。
0. C初识
0.1 第一个C程序
编写一个C程序总共分为4个步骤
创建项目创建文件编写代码运行程序
#include iostreamint main()
{using namespace std;cout…[TOC]
一、 C基础
C的IDE有CLion、Visual Studio、DEV C、eclipse等等这里使用CLion进行学习。
0. C初识
0.1 第一个C程序
编写一个C程序总共分为4个步骤
创建项目创建文件编写代码运行程序
#include iostreamint main()
{using namespace std;cout Come up and C me some time.;cout endl;cout You wont regret it! endl;return 0;
}C语言中省略int只写main()相当于函数的类型为int相当于int main(),C中不建议省略C中int main()等价于int main(void)C中main函数可以省略return 0;
0.2 注释
作用在代码中加一些说明和解释方便阅读代码
两种格式
单行注释//注释 通常放在一行代码的上方或者一条语句的末尾对该行代码进行说明 多行注释/*多行注释*/ 通常放在一行代码上方对该行代码进行整体说明 编译器在编译代码时会忽略注释的内容 0.3 变量
作用给一段指定的内存空间起名方便操作这段内存
语法数据类型 变量名 初始值;
变量声明的语法
typename variablename;int year;初始化
//C98中可以使用初始化数组的方式初始化变量
int num {10};
//C11中这种用法更常见,也可以不使用
int num{10};
//{ }也可以不包含东西
int num{};//此时变量的值初始化为0在C11中可以将 { }大括号初始化器用于任何类型({}前面可以使用也可以不使用)这是一种通用的初始化语法 0.3常量
作用用于记录程序中不可更改的数据
C定义常量的两种方式
#define 宏常量#define 常量名 常量值 通常在文件上方定义表示一个常量 const修饰的变量const 数据类型 常量名 常量值 通常在变量定义前加关键字const修饰该变量为常量不可修改
exp
//1、宏常量
#define day 7;int main()
{cout一周总共有day天endl;//day 8;会报错//2. const修饰变量const int month 12;cout一年总共有month个月endl;//month 24;报错常量不可更改
}0.4 关键字
作用关键字时C中预先保留的标识符
C关键字如下
asmdoifreturntypedefautodoubleinlineshorttypeidbooldynamic_castintsignedtypenamebreakelselongsizeofunioncaseenummutablestaticunsignedcatchexplicitnamespacestatic_castusingcharexportnewstructvirtualclassexternoperatorswitchvoidconstfalseprivatetemplatevolatileconst_castfloatprotectedthiswchar_tcontinueforpublicthrowwhiledefaultfriendregistertruedeletegotoreinterpret_casttry 提示在给变量或者常量起名称时候不要用C得关键字否则会产生歧义。 0.5 标识符命名规则
作用C规定给变量和常量命名有一定的规则
标识符不能是关键字标识符只能由字母、数字、下划线组成第一个字符必须为字母或下划线标识符中字母区分大小写 建议给标识符命名时争取做到见名知意的效果方便自己和他人的阅读 1. 数据类型和运算符
1.1 数据类型
创建一个变量或者常量时必须指出相应的数据类型否则无法给变量分配内存空间
数据类型存在的意义是方便编译器分配空间
1.1.1 整型
变量类型所占字节大小short至少16位 64位windows下sizeof short 2字节16位同下int至少与short一样长 sizeof int 432位long至少32位且至少与int一样长 sizeof long 432位long long至少64位且至少与long一样长 sizeof long long 864位 无符号类型 在变量类型前加unsigned可以使之称为无符号类型的变量如果short类型变量所占字节大小为2所占位数16位则第一位用于表示变量的正负则可用位数为15位能表示的范围为-215~215如果添加了unsigned关键字则无符号位可以表示的数据大小为0~2^16无负数。 unsigned是unsigned int的缩写 无符号类型的优点是可以增大变量能够存储的范围(仅当数值不为负时) 一个整型数据在不设置数据类型的情况下默认为int型如果不想使用int可以通过设置后缀可以为一个值确定数据类型 unsigned可以用u表示
long可以用L表示
unsigned和long的后缀可以叠加使用
ul表示unsigned long
ull表示unsigned long long不区分大小写 查看数据类型所占空间大小使用sizeof关键字 int main() {cout short 类型所占内存空间为 sizeof(short) endl;cout int 类型所占内存空间为 sizeof(int) endl;cout long 类型所占内存空间为 sizeof(long) endl;cout long long 类型所占内存空间为 sizeof(long long) endl;system(pause);return 0;
}C中进制的表示
十进制八进制以0开头十六进制以0x开头 不管数据以几进制的形式表现出来在内存中都是以二进制进行存储 C中以四种进制进行输出
#include iostream
#include bitsetusing namespace std;int main()
{int a64;cout(bitset32)aendl;//二进制32位输出coutoctaendl;coutdecaendl;couthexaendl;return 0;
}1.1.2浮点型实型
浮点数能表示带小数的数字
浮点数有两种表示方法 标准小数点表示法 exp: 3.14
12.3科学计数法 exp: 3.45e6
10.12E-6
5.98E10同科学计数E、e(不区分大小写)表示10的多少次方
int main()
{float f1 3.14f;double d1 3.14;coutf1endl;coutd1endl;coutfloat sizeof sizeof(f1)endl;coutdouble sizeof sizeof(d1)endl;//科学计数法float f2 3e2;//3*10^2coutf2 f2endl;float f3 3e-2;//3*0.1^2coutf3 f3endl;}3种浮点类型
float至少32位64位windows中sizeof float 4字节32位,7位有效数字double至少48位sizeof double 864位15~16位有效数字long doublesizeof long double 16128位 默认情况下浮点数是double类型的如果想使用float类型可以在值后面加上后缀F或者f 使用long double类型可以在值后面加上L或者l因为l与1不好分辨建议使用L 1.1.3 字符型
作用字符型变量用于显示单个字符
语法char ch a; 使用单引号单引号内只能有一个字符 char类型常用于表示字符与小整数大小为1个字节表示范围-128~127
char类型也可以使用unsigned关键字修饰unsigned char表示的范围是0~255
char型使用单引号 ’ ’ 括起来
exp:
char ch a;字符型变量并不是把字符本身放到内存中而是将对应的ASCII编码放入存储单元
ASCII码表格
ASCII值控制字符ASCII值字符ASCII值字符ASCII值字符0NUT32(space)6496、1SOH33!65A97a2STX3466B98b3ETX35#67C99c4EOT36$68D100d5ENQ37%69E101e6ACK3870F102f7BEL39,71G103g8BS40(72H104h9HT41)73I105i10LF42*74J106j11VT4375K107k12FF44,76L108l13CR45-77M109m14SO46.78N110n15SI47/79O111o16DLE48080P112p17DCI49181Q113q18DC250282R114r19DC351383S115s20DC452484T116t21NAK53585U117u22SYN54686V118v23TB55787W119w24CAN56888X120x25EM57989Y121y26SUB58:90Z122z27ESC59;91[123{28FS6092/124|29GS6193]125}30RS6294^12631US63?95_127DEL
ASCII 码大致由以下两部分组成
ASCII 非打印控制字符 ASCII 表上的数字 0-31 分配给了控制字符用于控制像打印机等一些外围设备。ASCII 打印字符数字 32-126 分配给了能在键盘上找到的字符当查看或打印文档时就会出现。
转义字符
作用用于表示一些不能显示出来的ASCII字符
exp:
转义字符含义ASCII码值十进制\a警报007\b退格(BS) 将当前位置移到前一列008\f换页(FF)将当前位置移到下页开头012\n换行(LF) 将当前位置移到下一行开头010\r回车(CR) 将当前位置移到本行开头013\t水平制表(HT) 跳到下一个TAB位置009\v垂直制表(VT)011\\代表一个反斜线字符092’代表一个单引号撇号字符039代表一个双引号字符034?代表一个问号063\0数字0000\ddd8进制转义字符d范围0~73位8进制\xhh16进制转义字符h范围09afA~F3位16进制
示例
int main()
{cout\\endl;cout\tHelloendl;cout\nendl;
}1.1.4 布尔型 bool
作用布尔数据类型代表真或假的值
语法bool x;
布尔型有两个值
true —真(本质是1)false —假(本质是0)
占用一个字节
exp:
int main()
{bool flag true;coutflagendl;//1flag false;coutflagendl;//0coutsizeof bool sizeof(bool)endl;
}1.2 类型转换
三种类型转换
初始化和赋值时进行的转换表达式中包含不同的类型时进行的转换参数传递给函数时进行的转换
1.2.1. 初始化类型转换
C允许将一种类型的值赋给另一种类型的变量这时值会自动转换为变量的类型
exp:
int a 1.23;//double赋给int结果转换为int值为1
float a 1;//int赋给float结果转换为float 值为1.0将表示范围小的类型值赋给表示范围大的类型没有问题将表示范围大的类型的值赋给表示范围小的类型时会出现失真 2. 表达式中的转换
当表达式中存在多种类型时一些类型会自动进行类型转换一些类型在遇到某些类型时才进行转换。
整型提升bool、unsigned char、char、short会自动转换为int
当表达式中存在表示范围比int大的类型时较小的类型会转换为较大的类型。
3. 传递参数时
传递参数时对char、short进行整型提升
强制类型转换
语法
(typename)value来自C语言的用法typename(value)来自C的用法使其更像是函数的用法static_casttypename (value) C中的强制转换符
exp:
(int)1.5;//C语言中的方式
int(1.5);//C中的方式在C中使用typeid(typename).name()查看数据类型名称 exp: couttypeid(int).name();//使用类型名查看int a;
couttypeid(a).name();//使用变量名C中的缩窄转换
C中的列表初始化使用{}进行初始化不允许缩窄转换(narrawing convertions)即变量的类型无法表示赋给它的值。
从浮点数转换为整数从取值范围大的浮点数转换为取值范围小的浮点数在编译期可以计算并且不会溢出的表达式除外从整数转换为浮点数在编译期可以计算并且转换之后值不变的表达式除外从取值范围大的整数转换为取值范围小的整数在编译期可以计算并且不会溢出的表达式除外
C中的auto声明
auto是C11中根据初始值类型推断变量的类型的关键字
作用在初始化声明中如果使用auto而不指定变量的类型编译器会把变量的类型设置成与初始值相同
常用于STL中
exp:
vectordouble scores;
vectordouble::iterator v scores.begin();
//可以简写为
auto v scores.begin();算术运算符
加法 减法- 乘法* 除法/
除法分为
整数和整数
即使使用float接收参数结果也是取整的因为运算符进行运算时就已经根据运算规则进行了取整
C
float a 9/2;
coutaendl;结果
4整数和浮点数
根据运算规则操作数会自动类型转换int转float再运算
浮点数和浮点数 求模% 算术运算符的优先级
递增递减运算符
自增–自减
组合赋值运算符
-*/%
关系运算符
关系运算符不能用于C风格的字符串可以用于string类型的字符串对于关系表达式如果判断结果为真则返回true如果为假返回false对于C风格的字符串如果使用 运算符进行判断则判断两者的地址是否相等。如果要判断内容是否相等可以使用strcmp()函数该函数接收两个字符串地址作为参数。
小于小于等于大于大于等于等于!不等于
2. 数组
数组的声明和初始化
数组的声明typename arrayname[arraySize];
exp:
int array[10];数组的初始化
使用逗号分隔的值列表(初始化列表){}
int array[3] {1,2,3};使用数组下标为数组元素赋值
int array[3];
array[0] 0;
array[1] 1;
array[2] 2;如果初始化数组时 [ ] 中为空则编译器自动计算元素个数
int array[] {0};注意事项 只有在数组定义时才能进行初始化。 如果不为数组赋值则数组中的值则是该数组内存单元中遗留的数据。 只要将数组的一部分进行初始化则编译器将其他的部分初始化为0如果初始化一个为0的数组则将首个元素设置为0即可。
C11数组初始化
C11中新增列表初始化( { } )的方式
可以不加 不允许缩窄转换可以在大括号内不包含任何东西,所有的值初始化为0
exp:
int array[] {};二维数组
二维数组的定义方式
int arr[][4] {{1,2,3},{4,5,6},{7,8,9},{10,11,12}
};int arr2[3][4] {1,2,3,4,5,6,7,8,9,10,11,12};两个中括号必须有一个有值否则编译器无法确定二维数组的排列方式 二维数组在内存空间中的排列方式与一维数组一致都是连续的内存空间编译器将其分为几个一维数组并且每个一维数组中的元素又包含了一个一维数组 在一维数组中数组名是指向数组的起始地址的指针也是第1个元素的地址在二维数组中同理数组名arrarr[0],因为arr[0]也是一个一维数组所以数组名也等于arr[0][0],但本质上还是arr[0][0]的地址。因为对arr解引用两次才能取到值 使用指针的方式访问数组中的元素 *(*(arr1)1)取的是arr[1][1]的值arr:arr是一个指针指针存放的是arr[0]的地址arr1:指针1表示指针指向的地址向后移动1 * sizeof(typename)位*(arr1):指针解引用出来是arr[1]这个地址同时arr[1]也是一个一维数组arr[1]相当于数组名也是个指针指向arr[1]这个数组的第一个元素arr[1][0]*(arr1)1:arr[1]这个数组所指向的地址arr[1][0]向后移动1 * sizeof(typename)位指向arr[1][1]这个地址*(*(arr1)1):将指向arr[1][1]这个地址的指针解引用得到arr[1][1]这个地址中的值字符串
字符串是一个或多个字符的序列是用双引号括起来的。
exp
Today is Thursday!C语言中没有指定存储字符串的基本数据类型C语言中的字符串都是由char类型的数组组成的字符串的末尾一定是以’\0’结束的因此数组的容量必须比字符的数量多1
字符串有两种声明方式
使用char数组C语言的方式使用string类C的方式
C风格的字符串
char str[] xxx;
char str[] {a,b,c};char *str abcd;因为C风格的字符串本质是一个数组所以除了使用引号的方式进行声明之外还可以使用数组的初始化方式进行初始化。 C风格的字符串与char数组的区别是字符串有内置的结束字符 \0 C风格的字符串
string str xxx;C中使用string类需要包含string头文件头文件iostream中已经隐式的包含了这个头文件。 string类在命名空间std中 string类相比于char数组有很多优势比如
可以将一个字符串赋值给另一个字符串可以使用等操作符不用考虑数组内存不足的问题
String 类型对象包括三种求解字符串长度的函数size() 和 length()、 maxsize() 和 capacity()
size() 和 length()这两个函数会返回 string 类型对象中的字符个数且它们的执行效果相同。max_size()max_size() 函数返回 string 类型对象最多包含的字符数。一旦程序使用长度超过 max_size() 的 string 操作编译器会拋出 length_error 异常。max_size 4611686018427387903capacity()该函数返回在重新分配内存之前string 类型对象所能包含的最大字符数
3. 结构体
语法
struct stname{typename1 variable1;typename2 variable2;
};stname s {value1,value2};//C中的初始化列表couts.variable1endl;//使用成员运算符 . 访问结构体元素C中初始化结构体时可以省略struct关键字 而c语言中需要加struct关键字 常用typedef关键字与结构体一起使用
typedef struct student{string name;int age;
}stu;//给student这个结构体起一个别名结构体的内存对齐 结构体成员的内部起始地址能够被其所占字节的大小整除 结构体的大小是最大成员所占字节的整数倍 对于结构体中的结构体要按照结构体展开之后的内存对齐来处理 人为制定规则#pragma pack(n)按照n进行对齐覆盖第一条规则如果n比原来的规则大则用小的规则 #pragma pack(2)
struct test{char a;int b;char c;
};//结果是2428不内存对齐用于单片机等 #pragma pack(1)
结构体数组
声明
stname s[] {{value1,value2},{v1,v2}
};结构体指针结构体数组结构体指针数组
#include iostreamusing namespace std;struct pstruct
{int a;double b;
};
int main()
{//创建两个结构体变量pstruct s1 {1, 3.14};pstruct s2 {2, 3.14};//创建两个结构体指针pstruct *ps1 s1;pstruct *ps2 s2;//创建一个结构体数组pstruct sarr[2] {s1,s2};//创建一个结构体指针数组pstruct *sparr[2] {ps1,ps2};}}
4.共用体union
共用体与结构体类似能存储多种数据类型但是同时只能存一种数据类型。 union u{int i_value;float f_value;double d_value;};u u1{};u1.d_value3.14;共用体常用于节省内存
5. 枚举
枚举
enum spectrum {red,black,green,blue};6. 指针
指针是一种变量指针中存储的值为地址。
指针的声明
typename* p;
exp:
int *p;//C语言中常用的格式
int* p;//C中常用的格式
int * p;//
int*p;//这几种格式都可以int* p1,p2;//注意: p2的类型是int对于每个指针变量名都需要使用一个*int的意思是 指针是指向int类型数据的指针 指针的初始化
int* p 地址;//因为指针中存储的是地址所以初始化需要给指针一个地址可以使用 变量 的方式,也可以使用数组名等其他方式总之需要是一个地址。如果不为其赋初值最好使其指向NULL防止野指针。指针中处理存储数据的策略是解引用*运算符被称为间接值(indirect value)或解除引用(dereferencing)运算符,将其放在指针变量的前面可以获得指针指向的地址中存储的值
指针常量和常量指针
左定值右定向 const在*左边值是常数const在*右边指针的指向是固定的 指针常量
指针常量的含义是指针类型的常量指针常量中指针自身的值是一个常量即指针指向的地址是一个常量不可修改在定义时必须初始化 int a 10,b 20;int* const p a;couta aendl;cout*p *pendl;//p b;报错cannot assign to variable p with const-qualified type int *const不能复制给具有const属性的变量p*p 20;//可以修改地址指向的值但是不可以修改地址cout修改后*p *pendl;a 10 p 10 修改后p 20 可以修改指针指向的值不可以修改地址 const在 p 前就是修饰pp是地址所以地址不可以改 常量指针
常量指针的含义是指向常量的指针 int a 10,b 20;const int *p a;cout*p *pendl;p b;//可以修改指针的指向//*p 20;报错Read-only variable is not assignable 只读变量不可赋值const修饰符
const限定符用于将变量变为常量
初始化时一定要赋值 const int c1 sum(1,2);//运行时初始化const int c2 c1;const int c3 1;//编译时初始化const int c4;//不正确const与引用 const int num 10;int c1 num;//报错因为引用可以修改变量值但是这是个常量不能修改需要加const修饰const int c1 num;double pi 3.14;
const int c1 pi;实际上发生了自动类型转换
int temp pi;
const int c1 temp;extern关键字
作用当在一个文件中声明了一个变量而想在所有文件中使用的时候需要在变量定义前加extern关键字
new操作符
C中利用new在堆区开辟数据利用delete释放语法new 数据类型利用new创建的数据会返回改数据对应类型的指针
#include iostream
using namespace std;int* func()
{//利用new关键字开辟堆区int* p new int(10);return p;
}
int main()
{int* p func();cout *p endl;//不会自动释放cout *p endl;cout *p endl;//释放delete p;cout*pendl;//内存已经被释放了system(pause);
}使用delete只能删除使用new产生的内存 在堆区new一个数组
#include iostream
using namespace std;int* func()
{int* arr new int[10];//使用new关键字返回的是指针return arr;
}
int main()
{int *arr func();coutarr[0]endl;for(int i0;i10;i)//给数组赋值{arr[i] i1;}for(int i0;i10;i)//打印数组{coutarr[i]endl;}//释放数组delete[] arr;arr NULL;
}释放数组对象时使用delete[] 在C中数组名表示的是地址 指针指向的数组的访问
直接使用指针名[number]即可访问不需要*指针名
指针名1表示指针指向数组下一个元素解引用*(p1)表示下一个值在数组中数组名和指针的区别
数组名是常量而指针是变量
7. 循环和关系表达式
for循环
for(int i0;i5;i)
{cout第i次输出endl;
}程序执行的顺序 设置初始值初始化循环变量 i执行测试判断 i 是否符合要求执行循环体内容打印输出更新用于测试的值 i #include iostream
#include cstring
using namespace std;int main()
{char ch[] abcd;for(int i0, jstrlen(ch)-1; ij; i,--j){char temp;temp ch[i];ch[i] ch[j];ch[j] temp;}for(int i0;i4;i){coutch[i]endl;}
}基于范围的for循环C11ranged-base
C11中引入的新特性可以遍历容器或者其他序列的所有元素
语法for(声明:表达式){语句}
声明建议使用auto进行自动类型推断如果要修改值可以使用引用的方式
double arr {12.1,3.14,5.23,3.56};
for(double x:arr)
{coutxendl;
}for(double x:arr)//引用的方式
{coutxendl;
}while循环
while(条件表达式)
{循环体;
}do while循环
do…while与while的区别是不管表达式是否成立do while至少执行一次
do
{循环体;
}
while(条件表达式);do while循环至少执行一次
8. 分支语句和逻辑运算符
语法
if(条件表达式)
{语句;
}if-else语句
if(条件表达式)
{语句1;
}
else
{语句2;
}if else if else语句
if(条件表达式)
{语句1;
}
elseif(条件表达式2){语句2;}elseif(条件表达式3){语句3;}else{语句4;}其实是多个if else的嵌套一般写为下面这种形式 if(条件表达式)
{语句1;
}
else if(条件表达式2)
{语句2;
}
else if(条件表达式3)
{语句3;
}
else
{语句4;
}逻辑表达式
逻辑或 ||||两边有一个或者都为真时返回真可以使用or替代
用法
53||54因为||为顺序点所以运算符左边的表达式优先于右边的表达式 i 6 || i j;假设 i 的值为10则在 i 与 j 进行比较时i 的值为11 ||左边表达式为真时右边的表达式不会执行
#include iostreamusing namespace std;int main()
{1||cout||后边不执行endl;0||cout||后边执行endl;
}
//结果输出第二句**逻辑与**可以使用and替代
两侧的表达式都为真则返回真
第一个为假则直接返回假不再判断右侧表达式
用于确定取值范围
if(age17age35)
{...
}逻辑非也可以使用not替代
将后面的表达式真值取反
总结
和||优先级低于所有关系运算符和算术运算符
!的优先级高于所有关系运算符和算术运算符
顺序点
顺序点的解释
C中的顺序点有以下几个
1)分号;
2)未重载的逗号运算符的左操作数赋值之后(即’,处)
3)未重载的’||‘运算符的左操作数赋值之后(即’||处);
4)未重载的’运算符的左操作数赋值之后(即处);
5)三元运算符’? : ‘的左操作数赋值之后(即’?处);
6)在函数所有参数赋值之后但在函数第一条语句执行之前;
7)在函数返回值已拷贝给调用者之后但在该函数之外的代码执行之前;
8)每个基类和成员初始化之后;
9)在每一个完整的变量声明处有一个顺序点例如int i, j;中逗号和分号处分别有一个顺序点;
10)for循环控制条件中的两个分号处各有一个顺序点。
尽量保证在两个相邻顺序点之间同一个变量不可以被修改两次以上或者同时有读取和修改否则就会产生未定义(无法预测)的行为。
字符函数库cctype
头文件cctype
作用用于确定字符是不是大写字母数字标点等工作返回值为int但也可以当做bool类型用
包含的函数
函数作用isalpha(char)判断是否是字母ispunct(char)判断是否是标点符号isdigit(char)判断是否是数字isspace(char)判断是否是空白isalnum(char)字母或数字iscntrl(char)是否是控制字符isgraph(char)是否是除空白以外的字符isupper(char)是否是大写字母islower(char)是否是小写字母toupper(char)如果是小写返回大写形式否则返回该参数isxdigit(char)是否是16进制tolower(char)如果是大写返回小写形式否则返回该参数
三元运算符( ? : )
语法expression1 ? expression2 : expression3
如果expression1表达式结果为true整个表达式的值为expression2的值否则为exoression3的值
switch语句 switch (inter-expression)//inter-expression是一个返回值为整型的表达式{case 1:expression1;break;case 2:expression2;break;default:expression3;}switch语句和if else 语句的作用一样但是switch语句的效率更高一点当选项超过3个时优先选用if else 9. 文件读写
写文件
使用文件读写的主要步骤
包含头文件fstream创建一个ofstream对象将这个ofstream同一个文件关联起来像使用cout那样使用ofstream
#include iostream
#include fstream
using namespace std;int main()
{ofstream of;//创建一个输出流对象of.open(test.txt);//与文件关联起来if(of.is_open()){of这句话会输出到文件中;of.close;//关闭文件}
} 创建的文件对象和cout用法一样对象的方法也一样,setf(), precision() 运行程序之前要绑定的文件不存在则自动创建这个文件如果存在则覆盖写入在写文件之前还要判断文件是否打开 读文件
方法与写文件类似使用 istream 创建对象
#include iostream
#include fstream
using namespace std;int main()
{string s;ifstream ifs;ifs.open(test.txt);if(!ifs.is_open()){exit(-1);//如果文件打开失败退出程序}else{ifss;//读取文件内容给字符串scoutsendl;//输出sifs.close();//关闭文件}
}exit(-1)用于退出程序 I/O操作中的方法
方法作用open(文件名,)将文件对象绑定文件is_open()判断文件是否打开eof()判断是否读取到文件尾是返回truefail()如果最后一次文件读取发生了类型不匹配和读取到文件尾则返回truebad()如果文件受损或硬件问题则返回truegood()该方法在没有任何问题的情况下返回true
10. 函数
函数的作用是将一段经常使用的代码封装起来减少重复代码一个较大的程序一般分为若干块每个模块实现特定的功能
函数的定义
函数的定义一般主要有5个步骤
1、返回值类型
2、函数名
3、参数表列
4、函数体语句
5、return 表达式
语法
返回值类型 函数名 参数列表
{函数体语句return表达式}返回值类型 一个函数可以返回一个值。在函数定义中函数名给函数起个名称参数列表使用该函数时传入的数据函数体语句花括号内的代码函数内需要执行的语句return表达式 和返回值类型挂钩函数执行完后返回相应的数据
函数原型语句函数的声明
作用
在C使用函数时C编译器需要知道这个函数的参数类型和返回值类型C中使用函数原形来提供这些信息。当函数写在main函数之后时编译器从上往下执行在main函数中又调用了写的函数编译器又找不到函数的实现这时需要在main函数之前写一个函数原型告诉编译器有这个函数
例如
double sqrt(double);函数的声明可以多次但是定义只能有一次 默认参数
作用在调用函数时可以不传参使用默认的参数。
语法int func(const char *, int n 1);
如果有多个参数需要默认参数默认参数必须从右往左进行赋值实参按照从左到右的方式传参但是不能少传参
func(1, ,2);这是不允许的
占位参数
语法
#include iostreamusing namespace std;void func(int a;int )
{cout占位参数endl;
}
void func2(int a10, int 10)//占位参数也有默认值
{cout占位参数2endl;
}
int main()
{func(10,20);//占位参数也要传进去否则报错
}函数的调用
函数和数组
#include iostream
#define ArrSize 8
using namespace std;int sum_arr(int *,int);
int main()
{int cookies[ArrSize] {1,2,4,8,16,32,64,128};cout数组名cookies的地址是cookies大小为 sizeof(cookies)endl;int sum sum_arr(cookies,4);coutthe first sum sumendl;
// sum sum_arr(cookies4,4);
// coutthe last sum sumendl;
}int sum_arr(int arr[],int n)
{cout形参arr的地址是arr大小为 sizeof(arr)endl;
// couttypeid(arr).name()endl;int total 0;for(int i0; i n;i){total arr[i];}return total;}输出结果 数组名cookies的地址是0xc1dabff5f0大小为32 形参arr的地址是0xc1dabff5f0大小为8 the first sum 15 数组名就是一个地址作为参数传入函数中后编译器无法推断数组的大小所以需要传入数组元素的个数我们也可以指定元素的个数来进行求和等操作数组名作为地址传入函数中函数操作的是数组本身而不是数组的副本(比如int类型作为形参传入就是一个副本)这样可以不用再复制一个副本来占用内存空间函数原型的作用是告知编译器这个函数的返回类型以及参数类型以及提前告知编译器存在这个函数只不过在main函数之后让编译器不要报错。参数类型可以只写类型不写变量名函数定义在main函数之后需要使用函数原型如果函数定义在main函数之前则不需要函数原型 使用const保护数组
因为数组作为参数传入数组不是以副本的形式传入的而是传入地址而使用地址能够修改地址中的值所以如果在函数中使用数组又不想误操作修改数组的话可以使用const关键字
void show_array(const double arr[], int n)
{for(int i0;in;i){coutarr[i]endl;//arr[i];编译器将会报错}
}注意使用const修饰之后数组在函数范围内是只读的并不会修改数组本身的可读可写性 除了使用数组地址元素个数的方式进行传参还可以使用首地址尾地址数组区间的方式进行传参即传入首地址尾地址就能够确定数组的大小这也是STL中使用的方法
exp
#include iostream
#define ArrSize 8
using namespace std;int sum_arr(int *,int*);
int main()
{int cookies[ArrSize] {1,2,4,8,16,32,64,128};cout首地址cookiesendl尾地址cookiesArrSizeendl;int sum sum_arr(cookies,cookiesArrSize);coutsum sumendl;
}int sum_arr(int* begin,int* end)
{int total 0;for(int i0; beginiend; i){total *(begini);//*解引用的优先级高于需要使用 ( )}return total;/*for(int pbegin; p!end; p){total *p;}*/}结果 首地址0x3fa6dff7b0 尾地址0x3fa6dff7d0 sum 255 首地址为****b0 尾地址为****d0 (d-b)*16^1 32 函数和C风格字符串
函数和指针
函数名(不加括号)就是函数的地址
handel(test);//参数为函数地址
handel(test());//参数为函数的返回值声明一个函数指针
//先声明一个函数
double func(int);//声明一个函数指针
double (*pf)(int)pf func;(*pf) func所以(*pf)和func的作用一致
所以pf是函数指针指向的是func的地址c也允许使用函数指针作函数使用不必使用(*pf)进行调用而是pf 回调函数
在一个函数中传入另一个函数并调用这个函数。
现阶段理解的回调函数的作用(也算是实际使用场景)是当一个线程在循环执行时因为不能及时的返回运行状态因此可以使用回调函数返回当前程序的状态
//这是函数指针的一个例子
#include iostreamusing namespace std;double a_estimate(int);//声明a估计的函数
double b_estimate(int);//声明b估计的函数
void estimate(int, double(*)(int));//声明调用估计的函数int main()
{estimate(1000,a_estimate);estimate(1000,b_estimate);
}double a_estimate(int line)
{return 0.05 * line;
}double b_estimate(int line)
{return 0.01 * line 0.1 * 0.2 * line;
}void estimate(int line,double (*pf)(int))
{coutline行代码需要pf(line)小时endl;coutline行代码需要(*pf)(line)小时endl;
}函数数组
#include iostreamusing namespace std;
//函数指针数组(指针数组)
double fa(int n);
double fb(int n);int main()
{double (*p[2])(int) {fa, fb};coutp[0](1)endl;coutp[1](2)endl;
}
double fa(int a)
{return a;
}double fb(int a)
{return a;
}C中cout无法直接通过函数名输出函数地址可以通过强制类型转换输出函数地址 int func(int);
cout(int*)funcendl;声明函数指针只需要将函数声明换为(*p)即可声明一个函数指针 double func(int);
double (*p)(int);递归
递归程序的基本结构
void recurs(arguments)
{statement1;if(test){recurs(arguments)}statement2;
}内联函数
语法
在函数声明和定义之前加关键字inline
内联函数的介绍
一般的函数在被调用的时候会跳转到存放函数的地址调用结束后再返回。内联函数直接将再调用函数的位置创建一个该函数的副本不需要进行跳转程序运行速度更快如果函数被调用十次就会被创建十个副本需要消耗更多的内存。是典型的以空间换时间。 语法 在函数声明和定义之前加关键字inline
#include iostreamusing namespace std;inline double my_inline_square(double x)
{return x*x;
}
int main()
{coutmy_inline_square(5)endl;
}如果函数代码执行的时间比函数跳转的时间短则可以节省跳转所消耗的大部分时间。 反之则无法节省时间还会浪费内存空间
在C语言中一般通过宏定义来实现这样的功能在C中可以使用内联函数来替代
#include stdio.h
#define SQUARE(x) (x)*(x)
int main()
{printf(%d,SQUARE(5));reutrn 0;
}函数重载
重载也叫多态是面向对象程序设计三大特点之一意思是一个函数可以有多种形态就像呵呵有两层意思一样。
编译器只根据函数的参数列表进行区分函数的多种形态因此函数的返回值可以不同。
在没有重载的时候当我们传入参数与形参类型不同的时系统可能会自动类型转换而重载后如果输入参数类型与形参列表类型不符则不会自动类型转换并且报错。
#include iostreamusing namespace std;
void print(int);
int print(double);
int print(string);
int main()
{unsigned int a 1;
// print(a);报错print(1);print(1.0);print(abcdefg);
}void print(int n)
{coutnendl;
}int print(double n)
{coutnendl;return 1;
}
int print(string n)
{coutnendl;return 1;
}
重载的每个函数都需要声明
函数模板
C面向对象的一个思想是泛型编程减少代码量增加复用性。
语法template typename T
#include iostream
using namespace std;
template typename T//或者使用template class T//T的名称可以随便起早期的C只有class后期才有的typename
void mySwap(T a,T b);int main()
{int a 10;int b 20;mySwap(a,b);couta a\nb bendl;double c 1.0;double d 2.0;mySwap(c,d);coutc c\nd dendl;
}template typename T
void mySwap(T a,T b)
{T temp a;a b;b temp;
}模板函数声明的时候也需要带上模板关键字 函数模板的作用一个函数需要重复使用且重复使用时参数类型不一样时可以使用函数模板减少重复代码。
函数模板并没有创建一个函数而是一个用于生成函数定义的方案
在使用的时候实例化为一个函数mySwap(1,2)这种方式称为隐式实例化
显式实例化语法template void mySwapint(int, int);
模板的使用方式有两种
自动类型推导手动类型选择
自动类型转换即上述代码使用的方式mySwap(a,b)编译器会自动类型推导
手动类型选择语法mySwap类型名(参数列表)即自己选择参数类型
显式具体化
显式具体化可以对结构进行操作
语法
template void swap(int,int);示例
struct job{string name;int salary;
};
template typename T
void swap(T a,T b)
{T temp a;a b;b temp;
}template void swap(job a,job b)
{int temp a.salary;a.salary b.salary;b.salary temp;
}int main()
{job a {james,100};job b {tom,2000};swap(a,b);//使用显式具体化swapint(a.salary,b.salary);//显示实例化swap(a.salary,b.salary);//使用隐式转换
}使用模板时必须确定出通用数据类型T并且能够推导出一致的类型否则会报错
decltype关键字
引用
给一个变量起别名语法数据类型 别名 原名;
#include iostreamusing namespce std;
int main()
{int a 10;int b a;b 20;coutaendl;
}注意事项
引用必须初始化引用在初始化后不可改变
引用一般用于函数参数不需要创建副本即可使用和修改原始数据。 在c语言中讲解形参的一个典型例子是交换两个值的函数现在使用引用的方式展示一下
#include iostreamusing namespace std;void cswap(int *pa,int *pb)
{int temp *pa;*pa *pb;*pb temp;
}void cppswap(int a,int b)
{int temp a;a b;b temp;
}int main()
{int a 0;int b 1;cppswap(a,b);cout\ a a\endl;cout\ b b\endl;cswap(a,b);cout\ a a\endl;cout\ b b\endl;
}c11新特性使用进行右值引用 double res std::sqrt(36); 引用也常用于结构体和类 在使用引用作为函数参数且不修改值的情况下建议搭配const关键字进行修饰防止误改数据。 引用也可作为返回值使用链式编程
例
#include iostreamusing namespace std;struct person
{string name;int age;
};
void showPerson(const person );
person addAge(person,person);
int main()
{person p1 {孙悟空,18};person p2 {猪八戒,20};person p3 {沙和尚,30};person p {total};showPerson(addAge(addAge(addAge(p,p1),p2),p3));
}
void showPerson(const person p)
{cout姓名p.name\n年龄p.ageendl;
}
person addAge(personp1,personp2)
{p1.age p1.agep2.age;return p1;
}命名空间
C的新特性命名空间
命名空间有三种格式
using namespace std
可以直接使用std命名空间中的coutcin等等
using std::cout
只能使用cout
std::cout
使用时使用std::cout 二、 C核心
1. 分文件编写程序
C\C是编译型语言运行程序之前需要编译代码将程序源码编译为机器语言再通过链接器将.o文件链接起来生成可执行文件。编译型语言比较依靠编译器。
单独编译 当程序比较大时需要分模块、分文件编写程序代码这样做的好处是每个文件都可以独立编译。当修改一个文件时只需要重新编译修改过的文件编译器包含编译器和链接器编译完成后通过链接器将相关文件链接起来。
怎么分模块写程序 通常将
函数声明#define或者const修定义的符号常量结构声明类声明模板声明内联函数
这种没有实例化或没有生成变量只是告诉编译器如何生成一个变量或者结构的代码放在.h文件中(const或#define除外) 一个程序被分为三部分
头文件(后缀.h)头文件的实现代码(后缀.cpp)源代码(主程序/调用头文件的程序,后缀.cpp)
#include中的 和
使用#include xxx.h包含头文件编译器会优先在工程目录下搜索该头文件找不到再从C标准库中找而使用 时编译器直接从C库中寻找指定的头文件不会从工程目录下寻找。
C中的头文件保护机制
在同一个文件中只能将同一个头文件包含一次为了避免不小心导致的多次引用C/C引入了头文件保护机制只需要在头文件中使用
#ifndef _XXX_H_
#define _XXX_H_
//这里写头文件代码
...
#endif这样的格式即可保证不会重复引用。
当编译器第一次引用这个头文件的时候#ifndef的意思是if not define然后就会#define这个名称这个名称是没有在其他地方声明过的名称一般都使用大写字母下划线组成程序运行到endif结束。
当程序中第二次引用这个头文件的时候因为已经define过这个名称了所以#ifndef不成立直接跳过这个头文件不引用。
链接的内部性与外部性
一个变量可以在单文件中使用也可以在多文件中使用这就是链接的内部性与外部性。 不加static关键字且不在代码块大括号内定义变量时变量是外部性的只需要在使用这个变量的文件中使用extern关键字声明一下这个变量即可在另一个文件中使用。 加static关键字且不在代码块内变量的链接性变为内部链接性只能在单文件中使用。
单定义规则一个变量只能定义一次但是可以声明多次
如果在一个文件中声明了一个链接性为外部的变量而另一个文件中也要使用同名变量因为单定义规则这时我们无法在另一个文件中重新定义这个变量。因此我们需要使用static关键字将这个变量定义为内部性的变量即可正常使用。
单定义规则的一个例外是局部变量当在函数内部或者类内部(统称为代码块内部)定义一个与全局变量重名的函数时局部变量会隐藏(hide)掉全局变量此时如果想要访问重名的全局变量我们可以使用域作用解析运算符(::)来访问全局变量
总结
链接性分为内部链接性和外部链接性使用关键字static控制不加static关键字默认为外部链接性可在本程序的其他文件中访问加static关键字后变量为内部链接性仅在本文件中可以访问在一个文件使用另一个文件定义的变量需要使用关键字extern声明注意是声明不是定义不能初始化值。
全局变量
静态全局变量
全局常量
静态全局常量
局部变量
静态局部变量
局部常量
2. 内存分区模型 代码区存放函数体的二进制代码 全局区存放全局变量静态变量以及常量 栈区由编译器自动分配释放存放函数的参数值局部变量 堆区用new操作符由程序员分配和释放若程序员不释放程序结束时操作系统回收 意义:不同区域赋予不同的生命周期更灵活
程序执行前
代码区的特点
共享只读
全局区的特点
该区域的数据在程序结束后系统自动释放
#include iostream
using namespace std;
int g_a 10;
const int c_g_a 10;
int main()
{int a 10;static int s_a 10;cout局部变量的a的地址为aendl;cout全局变量的g_a的地址为g_aendl;cout静态变量的地址s_aendl;cout字符串常量的地址hello worldendl;cout全局常量的地址c_g_aendl;
}运行结果 局部变量的a的地址为0x6d1cfffafc 全局变量的g_a的地址为0x7ff6346b4010 静态变量的地址0x7ff6346b4014 字符串常量的地址0x7ff6346b5056 全局常量的地址0x7ff6346b5004 示例2
#include iostream
using namespace std;//定义全局变量
int g_a 1;
static int s_g_a 1;
const int c_g_a 1;
static const int s_c_g_a 1;int main()
{cout全局变量的地址endl;coutg_aendl;couts_g_aendl;coutc_g_aendl;couts_c_g_aendl;cout字符串常量的地址endl;couthello worldendl;//定义两个静态局部变量static int s_l_a 1;const static int s_c_l_a 1;//两个局部变量int l_a 1;const int c_l_a 1;cout静态局部变量的地址endl;couts_c_l_aendl;couts_l_aendl;cout局部变量的地址endl;coutl_aendl;coutc_l_aendl;
}运行结果 全局变量的地址 0x7ff7ec244010 0x7ff7ec244014 0x7ff7ec245004 0x7ff7ec245008 字符串常量的地址 0x7ff7ec245030 静态局部变量的地址 0x7ff7ec24500c 0x7ff7ec244018 局部变量的地址 0x8855ff64c 0x8855ff648 可以看到静态变量和静态局部变量在地址上是没有区别的定义在代码块内的静态变量也是全局存在的但是在使用过程中却不能作为全局变量使用 因此可以将静态局部变量作为一个记录函数状态的变量 静态局部变量在第一次使用前分配在程序结束后销毁 示例 #include iostream
using namespace std;const int Arsize 10;void strcount(const char* str);int main()
{char input[Arsize];//定义一个字符数组char next;cout请输入一行endl;cin.get(input,Arsize);while(cin){cin.get(next);while(next ! \n){cin.get(next);}strcount(input);cout请输入下一行(空行退出)endl;cin.get(input,Arsize);}cout再见endl;
}
void strcount(const char* str)
{static int total 0;//定义一个静态局部变量用于记录字符总数int count 0;cout\str\包含;while(*str)count;totalcount;coutcount个字符endl;cout总共输入total个字符endl;
}运行结果 请输入一行 asdfg asdfg包含5个字符 总共输入5个字符 请输入下一行(空行退出) dfg dfg包含3个字符 总共输入8个字符 请输入下一行(空行退出) count是一个局部变量total是一个静态局部变量 count用于记录每次传进函数的字符个数因为局部变量随函数的运行而创建随函数的结束而消失所以每次调用strcount这个函数的时候count都是重新赋值的。 而total是一个静态局部变量虽然在其他地方无法访问这个变量但因其存放在全局区随程序的结束而结束其值是全局存在的不会随函数的创建而创建消失而消失因此可以用来记录函数的状态 总结
C在运行前分为全局区和代码区代码区的特点是共享和只读全局区中存放全局变量、静态变量、常量常量区中存放const修饰的全局常量和字符串常量
程序执行后
栈区
栈区由编译器管理存放函数的参数 注意事项不要返回局部变量的地址 #include iostream
using namespace std;int* func()
{int a 10;return a;
}
int main()
{int* p func();cout *p endl;//第一次可以打印正确的数据是因为编译器保留cout *p endl;//第二次报错内存已经被释放了system(pause);
}堆区
由程序员分配和释放若一直不释放程序运行结束时释放利用new可以在堆中开辟新数据利用delete在堆区删除数据
#include iostream
using namespace std;int* func()
{//利用new关键字开辟堆区int* p new int(10);return p;
}
int main()
{int* p func();cout *pendl;system(pause);
}动态内存
动态内存是由new关键字分配内存和delete释放内存 注意new和delete关键字是一对运算符 3. 类和对象
类的三大特性
封装继承多态
通用的写法将类名大写
类的语法
#include iostream
#include string
using namespace std;class People //类的声明
{
private:string p_name;int p_age;
public:void setName(string name){p_name name;}string getName(){return p_name;}
};int main()
{People p;//类的实例化p.setName(james);coutp.getName()endl;
}封装
封装的意义
将属性和行为作为一个整体将属性和行为加以权限控制尽可能将公有接口与实现细节分开
语法
class People
{
private://权限int p_name;//行为和方法int func(){};
};示例设计一个圆类
#include iostream
using namespace std;class Circle
{
public:double radius;const double PI 3.14;double round;double c_round(){return 2 * PI * radius;}
};int main()
{Circle c1;c1.radius 10;coutThe round of circle is:c1.c_round()endl;
}类属性和行为的三种访问权限
名称权限public类内类外都可以访问private类内可以访问类外不可以访问protected类内可以访问类外不可以访问 protected和private的区别主要在继承中体现 保护权限子类可以继承 私有权限子类不可以继承 点和圆的关系示例
#include iostreamusing namespace std;class Circle
{
private:int c_x,c_y;int c_radius;
public:void setRadius(int radius){c_radius radius;}void setX(int x){c_x x;}void setY(int y){c_y y;}int getRadius(){return c_radius;}int getX(){return c_x;}int getY(){return c_y;}
};class Point
{
private:int p_x,p_y;
public:void setX(int x){p_x x;}void setY(int Y){p_y Y;}int getX(){return p_x;}int getY(){return p_y;}
};
void relationC2p(Circle c,Point p);
int main()
{Circle c;Point p;c.setX(10);c.setY(10);c.setRadius(1);p.setX(20);p.setY(20);relationC2p(c,p);
}
void relationC2p(Circle c,Point p)
{if((c.getX()-p.getX())*(c.getX()-p.getX()) (c.getY()-p.getY())*(c.getY()-p.getY()) c.getRadius()*c.getRadius()){couton the circleendl;}else if((c.getX()-p.getX())*(c.getX()-p.getX()) (c.getY()-p.getY())*(c.getY()-p.getY()) c.getRadius()*c.getRadius()){coutin the circleendl;}else{coutout of circleendl;}
}构造函数和析构函数
构造函数初始化析构函数清理
构造函数语法
类名( ){ }
特点
可以有参数可以重载创建对象的时候构造函数会自动调用且只会调用一次
构造器按参数分
有参构造无参构造(默认)
按类型分
普通构造拷贝构造
析构函数
~类名( ) { }
对象在销毁前会自动调用析构函数且只会调用一次
#include iostreamusing namespace std;
class Person
{
private:int age;
public:Person(){cout这是一个无参构造(默认构造)endl;}Person(int a){age a;cout这是一个有参构造endl;}Person(const Person p){cout这是一个拷贝构造endl;}~Person(){cout这是一个析构函数endl;}
}int main()
{//括号法Person p1;//默认构造函数调用调用默认构造函数时不加( )//Person p1(); 编译器会认为是一个函数声明Person p2(10);//有参构造调用Person p3(p2);//拷贝//显示法Person p4;Person p5 Person(10);//Person(10)是一个匿名对象Perosn p6 Person(p5);//不要使用拷贝初始化匿名对象//Person(p3) 编译器会认为是重定义p3//隐式转换法Person p7 10;Person p8 p7;
}深拷贝与浅拷贝
析构函数为在堆区开辟的数据做释放
#include iostreamusing namespace std;class Person
{
public:int m_age;int *m_height;Person(int age,int height){m_age age;m_height new int(height);}
// Person(const Person p)//默认拷贝构造器
// {
// m_age p.m_age;
// m_height p.m_height;
// }Person(const Person p)//自己写拷贝构造器实现深拷贝{m_age p.m_age;m_height new int(*p.m_height);}~Person()//析构函数{cout这是一个析构函数endl;if(m_height!NULL){delete m_height;m_height NULL;}}
};void test()
{Person p1(12,160);Person p2(p1);
}int main()
{test();coutaendl;
}值传递一个类会调用拷贝构造拷贝一个新的对象
值返回会返回一个拷贝对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rBcb2maZ-1688987036670)(image-20220923155843763.png)]
初始化列表
//初始化列表
Person(int a,int b,int c):m_a(a),m_b(b),m_c(c);静态成员
静态成员变量 编译阶段分配内存 类内声明类外初始化 不属于某个对象所有对象共享一份数据
访问方式因为所有对象共享一份数据所以除了使用对象访问还可以使用类名进行访问
通过对象进行访问
Person p;
coutp.nameendl;通过类名进行访问
Person::name静态成员函数
共享的只能访问静态成员
访问
通过对象访问
Person p;
p.func();通过类名访问
Person::func();const成员函数
如果实例化一个静态对象则需要在类里面的函数添加const关键字确保静态对象不会修改对象的值
const Person p(1,2,3);
p.show();//如果show函数没有添加const时这是不允许的
void Person::show() const {}//添加const关键字之后即可C对象模型和this指针
C空对象占用1字节用于占位
成员变量和成员函数是分开存储的
只有非静态成员变量属于类的对象其他的都不属于类的对象上
this指针
本质是一个指针常量指向的地址不可以改变
this指针是隐含在每个非静态成员函数内的一种指针不需要定义
作用
当形参和成员变量重名时通过this指针区分类的非静态成员函数中返回对象本身可以用return *this链式
示例
#include iostreamusing namespace std;class Person
{
public: int apple;Person(int apple){this-apple apple;}Person addApple(Person p)//如果使用Person作为返回值返回的是一个拷贝使用Person返回的是p本体{this-applep.apple;return *this;//this指向p解引用就是p这个对象}
};void test()
{Person p1(1);Person p2(2);p1.addApple(p2).addApple(p2);coutp1.appleendl;
}int main()
{test();
}空指针访问成员函数
空指针可以访问成员但是如果访问静态成员变量会导致程序无法运行可以在需要静态成员变量的函数添加一条判断语句保证代码的健壮性
#include iostreamusing namespace std;class Person()
{int m_Age;void showClassName(){coutclass name is Person;}void showPersonName(){if(thisNULL){return;}coutage m_Ageendl;}
};void test01()
{Person * p NULL;p-showClassName();p-showPersonName();
}
void main()
{test01();
}const修饰成员函数
常函数
在函数声明后面加const为常函数常函数内不可以修改成员属性加mutable后可以修改
常对象 声明对象前添加const 常对象只能调用常函数
#include iostreamusing namespace std;class Person()
{
//this指针本质是指针常量指针指向的地址不可以改变 Person * const this
//在函数后面添加const代表常函数相当于const Person * const this,值和地址都不可以改变
public:int m_Age;mutable m_B;void showPerson() const{//相当于this-m_age 10;m_Age 10;//报错m_B 10;//可以}
};void test01()
{const Person p;//在对象前加const成为常对象p.m_Age 10;//不可以修改p.m_B 10;//可以修改
}友元
关键字friend
三种实现
全局函数做友元
#include iostreamusing namespace std;class Building
{friend void goodGay(Building building);
public:string sittingRoom;
private:string bedRoom;
public:Building(){sittingRoom客厅;bedRoom卧室;}
};void goodGay(Building building)
{cout好机油正在访问building.sittingRoomendl;cout好机油正在访问building.bedRoomendl;
}int main()
{Building building;goodGay(building);
}类做友元
运算符重载
*可以用于定义一个指针也可以用作解析符也可以作为乘法运算一个符号在不同的地方有不同的作用就是运算符的重载
在一个类中进行运算符的重载需要使用运算符函数operator op(参数列表)
只能使用C语言中已经定义的符号
继承
继承优点减少代码重复
语法class 子类 : 继承方式 父类
子类也称为派生类
父类称为基类
#include iostreamusing namespace std;class BasePAge
{
public:void content(){}void foot(){cout这是一个底部栏endl;}void left(){cout这是一个左侧栏endl;}
};class CPP : BasePAge
{
public:void content(){cout这是Java学科的栏endl;}
};void test01()
{CPP p;p.content();}
int main() {test01();return 0;
}继承方式
公共继承保护继承私有继承 继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去父类中的私有成员属性都会被被编译器隐藏所以访问不到
#include iostreamusing namespace std;class Base
{
public:int m_A;static int m_B;protected:int m_C;
private:int m_D;
};int m_B 100;
class Son:Base
{};void test01()
{Son s;coutsizeof Son sizeof(s)endl;
}int main()
{test01();
}输出结果sizeof Son 12 继承中构造和析构顺序
子类继承父类后当创建子类对象时也会调用父类的构造函数
具体的执行顺序是
父类构造
子类构造
子类析构
父类析构同名成员处理
访问子类同名成员直接访问即可访问父类同名成员需要加作用域
同名函数处理
加作用域 如果子类中出现和父类同名的成员函数子类的同名成员会隐藏掉父类中的所有同名成员函数即使父类中的同名成员函数重载了也无法调用必须加作用域 #include iostream
using namespace std;class Base
{
public:Base(){m_A 100;}int m_A;void sayHi(){coutBase: Hello Worldendl;}
};class Son:public Base
{
public:Son(){m_A 200;}int m_A;void sayHi(){coutSon: Hello Worldendl;}
};void test01()
{Son s;cout s.m_Aendl;cout s.Base::m_Aendl;//加作用域访问父类成员
}void test02()
{Son s;s.sayHi();s.Base::sayHi();//加作用域访问父类函数
}
int main() {test01();test02();return 0;
}同名静态成员处理
与非静态成员使用方式一致但是静态成员的访问方式有两种
通过对象访问通过类名访问
#include iostreamusing namespace std;class Base {
public:int static m_A;static void func() {cout 这是Base endl;}
};int Base::m_A 100;class Son : public Base {
public:int static m_A;static void func() {cout 这是Son endl;}
};int Son::m_A 200;void test01() {Son s;cout s.m_A endl;//通过对象访问cout s.Base::m_A endl;//通过对象访问cout Son::m_A endl;//通过类名访问cout Base::m_A endl;//通过类名访问cout sizeof Base sizeof(Base) endl;cout sizeof Son sizeof(Son) endl;//调用同名函数func//通过对象访问s.func();s.Base::func();//通过类名的方式访问Son::func();Base::func();
}int main() {test01();return 0;
}多继承语法
class 子类 : 继承方式 父类1, 父类2...
多继承会引发父类中有同名重圆出现需要加做哦用于区分
#include iostreamusing namespace std;class Base1
{
public:int m_A100;
};
class Base2
{
public:int m_A200;
};
class Son:public Base1,public Base2
{
public:int m_C 300;int m_D 400;
};
void test01() {Son s;coutsizeof Son sizeof(Son)endl;//couts.m_Aendl;会报错找到多个couts.Base1::m_Aendl;couts.Base2::m_Aendl;
}int main() {test01();return 0;
}菱形继承
利用虚拟继承解决菱形继承的问题
继承之前加上关键字virtual变为虚继承被继承的称为虚基类
#include iostreamusing namespace std;class Animal{//虚基类
public:int m_Age;
};
class Sheep:virtual public Animal{//继承前加virtual后变为徐济成
};
class Tuo:virtual public Animal{
};
class Sheeptuo:public Sheep,public Tuo{
};
void test01() {Sheeptuo st;st.Sheep::m_Age 18;st.Tuo::m_Age 28;//coutst.m_Age;直接使用会报错可以通过加域名的方式使用但是这个属性还是会有两份怎么将其变为一份呢//加上virtual关键字,变量只有一份coutst.Sheep::m_Ageendl;//28coutst.Tuo::m_Ageendl;//28coutsizeof(Sheep)endl;//16coutsizeof(Tuo)endl;//16coutsizeof(Sheeptuo)endl;//24
}int main() {test01();return 0;
}现在类的内部结构 vbptr
v -virtual
b -base
ptr -pointer
虚基类指针sheeptuo继承的是sheep和tuo中的指针指向Animal中的m_Age总结
菱形继承带来的主要问题是子类继承两份相同的数据导致资源浪费利用虚拟继承可以解决菱形继承问题
多态
多态分为两类
静态多态函数重载和运算符重载属于静态多态复用函数名动态多态派生类和虚函数实现运行时多态
静态多态和动态多态的区别
静态多态的函数地址早绑定-编译阶段确定函数地址动态多态的函数地址晚绑定-运行阶段确定函数地址无法在编译阶段确定地址
#include iostreamusing namespace std;class Animal{
public:virtual void speak()//加virtual关键字成为虚函数{coutAnimal Speakendl;}
};
class Cat:public Animal{
public:void speak(){coutCat Speakendl;}
};
void dospeak(Animal animal)
{animal.speak();
}
void test01() {Cat c;dospeak(c);
}int main() {test01();return 0;
}动态多态满足条件
有继承关系子类要重写父类的虚函数
动态多态的使用
父类的指针或者引用 指向子类对象
底层
vfptr
v -virtual
f -function
ptr -pointer
虚函数表指针
指针指向一个表vftable虚函数表
v - virtual
f -function
table
表内部记录虚函数的地址纯虚函数和抽象类
纯虚函数语法
virtual 返回值类型 函数名 参数列表 0;
virtual void func() 0;当类中有了纯虚函数这个类也称为抽象类
抽象类特点
无法实例化对象子类必须重写抽象类中的纯虚函数否则也属于抽象类无法实例化对象
虚析构和纯虚析构
多态使用时如果子类中有属性开辟到堆区那么父类指针在释放时无法调用到子类的析构代码
解决方式将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象都需要有具体的函数实现
虚构和纯虚析构区别
如果是纯虚析构该类属于抽象类无法实例化对象
虚析构语法
virtual ~类名(){}
纯虚析构语法
virtual ~类名() 0;
虚析构函数就是用来解决通过父类指针释放子类对象
#include iostreamusing namespace std;
class Animal {
public:Animal(){cout Animal 构造函数调用 endl;}virtual void Speak() 0;//析构函数加上virtual关键字变成虚析构函数//virtual ~Animal()//{// cout Animal虚析构函数调用 endl;//}virtual ~Animal() 0;
};Animal::~Animal()
{cout Animal 纯虚析构函数调用 endl;
}//和包含普通纯虚函数的类一样包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。class Cat : public Animal {
public:Cat(string name){cout Cat构造函数调用 endl;m_Name new string(name);}virtual void Speak(){cout *m_Name 小猫在说话! endl;}~Cat(){cout Cat析构函数调用! endl;if (this-m_Name ! NULL) {delete m_Name;m_Name NULL;}}public:string *m_Name;
};void test01()
{Animal *animal new Cat(Tom);animal-Speak();//通过父类指针去释放会导致子类对象可能清理不干净造成内存泄漏//怎么解决给基类增加一个虚析构函数//虚析构函数就是用来解决通过父类指针释放子类对象delete animal;
}int main() {test01();return 0;
}内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放造成的内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。
案例
#include iostreamusing namespace std;class CPU
{
public:virtual void calculate() 0;
};
class VedioCard
{
public:virtual void display() 0;
};
class Memory
{
public:virtual void storage() 0;
};class IntelCPU:public CPU
{virtual void calculate(){coutIntel的CPU开始计算了endl;}
};
class IntelVedioCard:public VedioCard
{void display(){coutIntel的显卡开始工作了endl;}
};
class IntelMemory:public Memory
{void storage(){coutIntel的存储开始存储了endl;}
};class LenovoCPU:public CPU
{void calculate(){coutLenovo的CPU开始计算了endl;}
};
class LenovoVedioCard:public VedioCard
{void display(){coutLenovo的显卡开始工作了endl;}
};
class LenovoMemory:public Memory
{void storage(){coutLenovo的存储开始存储了endl;}
};class Computer
{
public:Computer(CPU *cpu, VedioCard *vediocard,Memory *memory) {m_cpu cpu;m_vc vediocard;m_mem memory;}void work(){m_cpu-calculate();m_vc-display();m_mem-storage();}~Computer(){if (m_cpu !NULL){delete m_cpu;m_cpu NULL;}if (m_vc !NULL){delete m_vc;m_vc NULL;}if (m_mem !NULL){delete m_mem;m_mem NULL;}}
private:CPU * m_cpu;VedioCard * m_vc;Memory * m_mem;
};
void test01()
{//组装第一台CPU *intelcpu new IntelCPU;VedioCard *intelvc new IntelVedioCard;Memory *intelmemory new IntelMemory;Computer c1(intelcpu,intelvc,intelmemory);c1.work();Computer c2 (new LenovoCPU,new LenovoVedioCard,new LenovoMemory);c2.work();
}int main()
{test01();
}#include iostream
#include fstreamusing namespace std;//创建抽象员工类
class Staff
{
public:int staffNum;int departNum;string staffName;
};
//创建老板类
class Boss:Staff
{
};
//创建经理类
class Manager:Staff
{
};
//创建员工类
class Employee:Staff
{
};
ofstream fs;
//添加员工的函数
void addStaff(Staff * staff)
{fs.open(./职工管理表.txt,ios::out|ios::app);if (fs.is_open()){cout请输入员工姓名:endl;cinstaff-staffName;cout请输入员工编号:endl;cinstaff-staffNum;cout请输入部门编号:endl;cinstaff-departNum;fsstaff-staffNameendl;fsstaff-staffNumendl;fsstaff-departNumendl;}else{cout文件打开失败endl;}
}//显示和选择函数
void show_choose()
{while (true){int choose;cout****************endl;cout欢迎使用职工管理系统endl;cout0.退出管理系统endl;cout1.增加职工信息endl;cout2.显示职工信息endl;cout3.删除离职职工endl;cout4.修改职工信息endl;cout5.查找职工信息endl;cout6.按照编号排序endl;cout7.清空所有文档endl;cout****************endl;cout请输入您的选择:endl;cinchoose;if (choose0)//退出管理系统{cout成功退出!endl;break;}else if (choose1)//增加职工信息{addStaff(new Staff);}else if (choose2)//显示职工信息{}else if (choose3)//删除职工信息{}else if (choose4)//修改职工信息{}else if (choose5)//查找职工信息{}else if (choose6)//按照编号排序{}else if (choose7)//清空所有文档{}else{cout输入有误请重新输入endl;}}}
int main()
{show_choose();
}三、 C提高
1. 模板
#include iostreamusing namespace std;template class NameType, class AgeType
class Person
{
public:Person(NameType name,AgeType age){this-m_name name;this-m_age age;}void showPerson(){coutname:this-m_nameendl;coutage:this-m_ageendl;}private:NameType m_name;AgeType m_age;
};void test01()
{Personstring,int p1(孙悟空,999);p1.showPerson();
}int main()
{test01();
}类模板和函数模板的区别
类模板没有自动类型推导类模板在模板参数列表中可以有默认参数
#include iostreamusing namespace std;
//类模板和函数模板的区别template class NameType string, class AgeType int//默认参数
class Person
{
public:Person(NameType name,AgeType age){this-m_name name;this-m_age 999;}void showPerson(){coutname: this-m_nameendl;coutage: this-m_ageendl;}private:NameType m_name;AgeType m_age;
};void test01()
{
// Person p(孙悟空,1000)错误的无法自动类型推导Personstring,int p(孙悟空,999);p.showPerson();
}
//1. 类模板没有自动类型推到
//2. 类模板在模板参数列表中可以有默认参数
void test02()
{Person p(猪八戒,999);
}
int main()
{test01();test02();
}类模板中成员函数的创建时机
类模板中的成员函数和普通类中成员函数创建时机是有区别的
普通类中的成员函数一开始就可以创建类模板中的成员函数在调用时才创建
#include iostreamusing namespace std;class Person1
{
public:void showPerson1(){coutPerson1 showendl;}
};class Person2
{
public:void showPerson2(){coutPerson2 showendl;}
};
templateclass T
class MyClass
{
public:T obj;//类模板中的成员函数在调用时创建void func1(){obj.showPerson1();}void func2(){obj.showPerson2();}
};void test01()
{MyClassPerson2 m;
// m.func1();编译出错说明函数调用时才会创建成员函数m.func2();
}int main()
{test01();
}类模板对象做函数参数
三种传入方式
指定传入的类型 --直接显示对象的数据类型参数模板化 --将对象中的参数变为模板进行传递整个类模板化 --将这个对象类型模板化进行传递
#include iostreamusing namespace std;
//类模板对象做函数参数
templateclass T1,class T2
class Person
{
public:Person(T1 name,T2 age){this-m_namename;this-m_ageage;}T1 m_name;T2 m_age;void personShow(){cout姓名 this-m_name;cout年龄: this-m_ageendl;}
};//1. 指定传入类型
void printPerson1(Personstring,int p)
{p.personShow();
}
void test01()
{Personstring, int p(孙悟空,10);printPerson1(p);
}//2. 参数模板化
templateclass T1, class T2
void printPerson2(PersonT1,T2 p)
{p.personShow();coutT1的类型为typeid(T1).name()endl;coutT2的类型为typeid(T2).name()endl;
}
void test02()
{Personstring, int p(猪八戒,20);printPerson2(p);
}
//3. 整个类模板化
templateclass T
void printPerson3(T p)
{p.personShow();coutT的类型为 typeid(T).name()endl;
}
void test03()
{Personstring ,int p(唐僧,30);printPerson3(p);
}
int main()
{test01();test02();test03();
}类模板与继承
当子类继承的父类是一个类模板时子类在声明的时候要指出父类中T的类型
#include iostreamusing namespace std;templateclass T
class Base
{T m;
};
class Son:public Baseint//继承时必须要知道父类T的类型才能继承给子类
{};void test01()
{Son s1;
}如果想灵活指定父类中T的类型子类也要写成一个类模板
#include iostreamusing namespace std;templateclass T
class Base
{T m;
};
class Son1:public Baseint
{};
templateclass T1,class T2
class Son2:public BaseT2
{
public:Son2(){coutT1的数据类型为: typeid(T1).name()endl;coutT2的数据类型为: typeid(T2).name()endl;}T1 obj;
};void test01()
{Son1 s1;
}
void test02()
{Son2int,char s2;
}int main()
{test01();test02();
}模板类中函数的类外实现
#include iostreamusing namespace std;
//类模板函数的类外实现
templateclass T1,class T2
class Person
{
public:T1 m_name;T2 m_age;Person(T1,T2);void showPerson();
};
templateclass T1, class T2
PersonT1,T2::Person(T1 name, T2 age)
{this-m_namename;this-m_ageage;
}
templateclass T1,class T2
void PersonT1, T2::showPerson()
{cout姓名 this-m_name年龄: this-m_ageendl;
}
void test01()
{Personstring,int p(孙悟空,100);p.showPerson();
}
int main()
{test01();
}总结类模板中成员函数类外实现时需要加上模板参数列表
类模板分文件编写
2. STL(Standard Template Library)标准模板库
面向对象和泛型编程都是为了提高代码的复用性STL就是为了提高数据结构和算法的复用性制作出来的
STL六大组件
容器
各种数据结构vector list deque set map
算法
各种常用的算法 sort find copy for_each
迭代器
容器和算法之间的胶合剂
仿函数
行为类似函数可作为算法的某种策略
适配器(配接器)
一种用来修饰容器或者仿函数或迭代器接口的东西
空间配置器
负责空间的配置与管理 容器和算法之间通过迭代器进行无缝连接 STL中的所有技术都采用了模板类或者模板函数 容器
STL容器就是将应用最广泛的数据结构表示出来
常用的数据结构数组 链表 树 栈 几何 映射表等等
容器分为序列式容器和关联式容器
序列式容器强调值的排序序列式容器的每个元素均有固定位置
关联式容器二叉树结构各元素之间没有严格的物理上的顺序
vector
#include iostream
#include vector
#include algorithmusing namespace std;
void myPrint(int val)
{coutvalendl;
}
void test01()
{//创建了一个vector容器vectorint v;//想容器中插入数据v.push_back(10);v.push_back(20);v.push_back(30);v.push_back(40);//通过迭代器访问容器中的数据vectorint::iterator itBegin v.begin();//其实迭代器vectorint::iterator itEnd v.end();//结束迭代器指向容器最后一个元素的下一个位置//第一种遍历方式while(itBegin!v.begin()){cout *itBegin endl;itBegin;}//第二种遍历方式for(vectorint::iterator itv.begin();it!v.end;it){cout*itendl;}//第三种遍历方式for_each(v.begin(),v.end(),myPrint);//回调函数
}
int main()
{test01();
}vector是STL中最常用的一种数据结构
特点 与数组类似但是数组是固定长度vector是长度可变的可以动态扩展的。 支持随机访问 动态扩展并不是在原有的空间进行扩展而是新建一个比原来更大的空间空间的大小由vector内部的算法确定
vector的构造函数
vectorT v;用类模板实现默认构造函数vector(v.begin(), v.end());将一个v[v.begin(),v.end())前闭后开的区间的内容给vectorvector(n,elem);将n个elem给vectorvector(const vector v);拷贝构造函数
#include iostream
#include vector
using namespace std;void printVector(vectorintv)
{for(const int n : v){coutn ;}coutendl;
}
void test01()
{vectorint v1;//默认构造for(int i0;i10;i){v1.push_back(i);}coutv1endl;printVector(v1);vectorint v2(v1.begin(),v1.end());//将v1迭代器区间的内容给v2coutv2endl;printVector(v2);vectorint v3(10, a);//n个elemcoutv3endl;printVector(v3);vectorint v4(v3);//拷贝构造coutv4endl;printVector(v4);
}int main()
{test01();
}vector赋值
函数原型
vector operator(const vector v);//重载操作符assign(v.begin(),v.end());将左闭右开区间[begin,end)赋值给本身assign(n,elem);将n个elem赋值给本身
示例
void test01()
{vectorint v1;for(int i0;i10;i){v1.push_back(i);}vectorint v2 v1;//等号操作符进行赋值vectorint v3;//将左闭右开区间赋值给本身v3.assign(v1.begin(),v1.end());vectorint v4;v4.assign(10,1);//n个elem}vector容器的容量和大小
函数原型
empty()判断容器是否为空capacity()容器的容量,vector容器的容量会比存放的数据多一点size()容器中元素的个数resize(int num)重新指定容器的大小如果容器变长原数据填充原位置若变短则超出容器范围的数据被删除resize(innt num,elem)重新指定容器的大小如果容器变长原数据填充原位置以elem填充原位置若变短则超出容器范围的数据被删除
void test01()
{vectorint v1;for(int i0;i10;i){v1.push_back(i);}coutv1是否为空v1.empty()endl;coutv1的容量v1.capacity()endl;coutv1元素个数v1.size()endl;v1.resize(5);printVector(v1);v1.resize(10,1);printVector(v1);
}vector的插入和删除
函数原型
push_back(elem)尾部插入元素pop_back()删除最后一个元素insert(const_iterator pos,elem)迭代器向指定位置pos插入元素eleminsert(const_iterator pos,int count,elem)向指定位置插入count个elemerase(const_iterator pos)删除指定位置元素erase(const_iterator begin,const_iterator end)删除从begin到end的元素clear()删除元素中所有的元素
vector数据的存取
函数原型
at(int index)获取index位置的元素operator[int index]获取index处的元素front()获取第一个元素back()获取最后一个元素
vector互换容器
函数原型
swap(vector v)容器本身与容器v互换
作用1收缩内存
void test01()
{vectorint v;for (int i 0; i 10000; i) {v.push_back(i);}coutv的容量v.capacity()endl;//v的容量16384v.resize(3);coutresize后的容量v.capacity()endl;//resize后的容量16384vectorint(v).swap(v);cout交换后v的容量v.capacity()endl;//交换后v的容量3
}vector预留空间
作用提前预留空间减少动态扩展容量时的扩展次数
函数原型
reserve(int len)预留len个空间
reserve只预留空间不初始化数据
如果vector要存放的数据比较多可以直接预留足够的空间不用一次次地动态扩展影响性能
void test01()
{vectorint v;int num 0;int *p nullptr;for (int i 0; i 10000; i) {v.push_back(i);if(p!v[0]){p v[0];num;}}coutv1动态扩展的次数:numendl;vectorint v2;int num2 0;int *p2 nullptr;v2.reserve(10000);for (int i 0; i 10000; i) {v.push_back(i);if(p!v.at(0)){p v.at(0);num2;}}coutv2动态扩展的次数:num2endl;
}string容器
string是C中用于管理字符的一个类
本质上字符在string类中是char *类型的变量只不过被封装成了一个类,这个类中重载了很多运算符使其像个数组一样。下面总结了一些string类的函数和重载的运算符
string的构造函数
string()默认构造
string(const char* s)字符串构造
string(const string s)拷贝构造
string(int num, char c)数值*字符构造
#include iostreamusing namespace std;void test01()
{const char* str Hello World;string s1;//默认构造string s2(str);//使用字符常量构造string s3(hello World);//同上string s4(s2);//拷贝构造string s5(10,a);//数量*字符couts1 s1endl;couts2 s2endl;couts3 s3endl;couts4 s4endl;couts5 s5endl;
}int main()
{test01();
}string的赋值操作
string operator(const char* s)
string operator(const string s)
string operator(const char c)
string assign(const char* s) 把字符串赋值给string对象
string assign(const char* s, int n) 把字符串前n个字符赋值给string对象
string assign(string s) 另一个string给这个string
string assign(int n,char c) n个字符
string字符串拼接
string operator(const char* s)
string operator(const string s)
string operator(const char c)
string append(const char* s)
string append(const string s)
string append(const char c)
string append(const string s, int pos, int n)
string查找和替换
函数原型 在类里面的函数后面加const使函数变为调用时不可修改类内部数据的函数 int find(const string str, int pos0) const //查找str第一次出现的位置从pos开始查找找不到返回-1 int find(const char* str,int pos0) const //同上 int find(const char* str, int pos0, int n) const //从pos位置查找str的前n个元素的位置 int find(const char c, int pos0) const //从pos位置查找字符c int rfind(const string str, int posnpos) const//查找str的最后出现的位置从pos开始查找 int rfind(const char* str,int posnpos) const //同上 int rfind(const char* str, int pos, int n) const //从pos位置查找str的前n个元素的位置 int rfind(const char c, int pos0) const //从pos位置查找字符c string replace(int pos, int n, const string str)从pos开始n个字符为字符串str string replace(int pos, int n, const char* s)
示例
void test01()
{string str abcdefef;int pos str.find(ef);coutposendl;//4int pos2 str.rfind(ef);coutpos2endl;//6str.replace(1,3,1111);coutstrendl;//a1111efef,从位置1开始后面的三个字符变为1111
}总结
find从左往右rfind从右往左
find返回查找的第一个字符找不到返回-1
replace在替换时将从哪个位置起多少个字符替换为 什么
string的字符串比较
按ASCII码进行比较
返回0
返回1
返回-1
test01()
{string str1 hello;string str2 world;if(str1.compare(str2)0){coutstr1str2endl;}
}string字符存取
char operator[](int n)[ ]方式char at(int)//at方式
test01()
{string str abcdefg;//第一种方式for(int i0;istr.size();i){coutstr[i]endl;}//第二种方式for(int i0;istr.size();i){coutstr.at(i)endl;}
}string插入和删除
函数原型
string insert(int pos, const char* s);//插入字符串string insert(int pos, const string str);插入字符串string insert(int pos, int n, char c);在指定位置插入n个字符string erase(int pos, int npos);删除从pos开始的n个字符
void test01()
{string str1 hello;string str2 world;str1.insert(5, );coutstr1endl;//hello空格str1.insert(6, str2);coutstr1endl;//hello world
}string子串
函数原型
string substr(int pos0, int nnpos) const;//返回由pos开始的n个字符串组成的字符串
示例
void test01()
{string str1 hello;string str2 str1.substr(0,2);coutstr2endl;
}deque容器
deque是一个双端数组可对头端进行插入
deque与vector的区别
deque的头部插入比vector快deque的访问速度不如vector deque内部原理 构造函数
函数原型
dequeT deqT;默认构造deque(beg,end);deque(n, elem);deque(const deque deq);
deque赋值
与vector类似
函数原型
deque operator(const deque deq);assign(begin,end);assign(n,elem);
deque大小
函数原型
deque.empty();判断容器是否为空deque.size();返回容器中元素的个数deque.resize(num);重新指定容器长度为num容器变长以0填充容器变短超出部分被删除deque.resize(num, elem);重新指定容器长度为num容器变长以elem填充容器变短超出部分被删除
deque插入和删除
函数原型
两端插入操作
push_back(elem);尾部插入push_front(elem);头部插入pop_back();删除最后一个数据pop_front();删除第一个数据
指定位置插入删除
insert(pos, elem); 在pos位置插入一个elem元素的拷贝返回数据的位置insert(pos,n,elem);在pos位置插入n个elem无返回值insert(pos, begin,end);在pos位置插入[begin,end)区间的数据无返回值clear();清空容器erase(begin, end);删除[begin,end)区间的数据返回邪恶一个数据的位置erase(pos);删除pos位置的数据返回下一个数据的位置
deque数据存取
函数原型
at(int index);返回索引index所指的数据operator[]返回索引index所指的数据front();返回第一个数据back();返回最后一个数据
deque排序操作
利用算法实现deque内部数据的排序
#include algorithm
函数原型
sort(iterator begin,iterator end);对beg,end区间内的元素进行排序
void test01()
{dequeint deq;deq.push_back(10);deq.push_back(20);deq.push_back(30);deq.push_front(100);deq.push_front(200);deq.push_front(300);//300 200 100 10 20 30printDeque(deq);sort(deq.begin(),deq.end());printDeque(deq);//10 20 30 100 200 300
}#include iostream
#include utility
#include vector
#include deque
#include algorithmusing namespace std;class Person;
void createPlayer(vectorPerson);void setScore(vectorPerson);void showScores(vectorPerson);class Person
{
public:explicit Person(string name, double score0): m_name(std::move(name)), m_score(score){}string m_name;double m_score;
};
int main()
{//1. 创建一个vector容器存放5个选手vectorPerson v;createPlayer(v);//测试
// for(auto p:v)
// {
// coutp.m_nameendl;
// }//2. 打分setScore(v);//3. 展示showScores(v);}void showScores(const vectorPerson v) {for(const auto it : v){cout姓名it.m_name 得分it.m_scoreendl;}
}void setScore(vectorPerson v) {srand((unsigned int)time(nullptr));//随机数种子for(auto it : v){dequeint scores;for (int i 0; i 10; i) {int score rand() % 40 60;scores.push_back(score);}scores.pop_front();scores.pop_back();int sum 0;for(int score : scores){sum score;}double average sum / scores.size();it.m_score average;}
}void createPlayer(vectorPerson v) {string nameSeed ABCDE;for (int i 0; i 5; i) {string name 选手;name nameSeed[i];Person p(name);v.push_back(p);}
}随机数种子
srand((unsigned int)time(nullptr));//随机数种子
rand() % 40 60//产生60~100的随机数Parameter ‘name’ is passed by value and only copied once; consider moving it to avoid unnecessary copies
explicit关键字
explicit的一个英文意思是显式的
explicit用于构造函数前使用该关键字后在参数列表有默认参数的情况下构造函数不能进行隐式转换
std::move函数
C 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了,通过std::move可以避免不必要的拷贝操作。 std::move是将对象的状态或者所有权从一个对象转移到另一个对象只是转移没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.。 对指针类型的标准库对象并不需要这么做.
stack 先进后出后进先出的结构只有一个出口 不允许遍历
构造函数
stackT stk//默认构造stack(const stack stk)//拷贝构造函数
赋值
stack operator(const stack stk);
数据存取
pop()//从栈顶移除第一个元素push()//向栈顶添加元素top()//返回栈顶元素
大小操作
判断栈是否为空empty()返回栈元素个数size()
queue容器 先进先出只能访问队头队尾元素不允许有遍历行为
构造函数
queueT que//queue采用模板类实现queue对象的默认构造queue(const queue que)//拷贝构造函数
赋值操作
queue operator(const queue que)//重载等号运算符
数据存取
push()//往队尾添加元素pop()//从对头移除第一个元素back()//返回最后一个元素front()//返回第一个元素
大小操作
empty()判断是否为空size()//返回队列的大小
list容器 将数据进行链式存储物理地址上是非连续的逻辑地址通过指针进行链接 链表由一系列结点组成 结点由存储数据的数据域和存储下一个结点地址的指针域 STL中的链表是双向循环列表
优点
可以对一个位置进行快速的插入或删除元素
缺点 遍历速度没有数组快 占用的空间比数组大
构造函数
listT lst;//默认构造list(beg,end);//构造函数将[begin,end)区间内的内容赋值给listlist(n.elem);//n个elemlist(const list lst);//拷贝构造
赋值和交换
assign(begin,end);//赋区间[begin,end)的值assign(n,elem);//n个elemlist operator(const list lst);//重载操作符swap(lst);//将自身内容与lst互换交换
大小操作
empty()判断容器是否为空size()容器中元素的个数resize(int num)重新指定容器的大小如果容器变长原数据填充原位置若变短则超出容器范围的数据被删除resize(innt num,elem)重新指定容器的大小如果容器变长原数据填充原位置以elem填充原位置若变短则超出容器范围的数据被删除
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值匹配的元素。 尾插 — push_back 尾删 — pop_back 头插 — push_front 头删 — pop_front 插入 — insert 删除 — erase 移除 — remove 清空 — clear
list 数据存取
功能描述
对list容器中数据进行存取
函数原型
front(); //返回第一个元素。back(); //返回最后一个元素。
list容器中不可以通过[]或者at方式访问数据
list 反转和排序
功能描述
将容器中的元素反转以及将容器中的数据进行排序
函数原型
reverse(); //反转链表sort(); //链表排序
void printList(const listint L) {for (listint::const_iterator it L.begin(); it ! L.end(); it) {cout *it ;}cout endl;
}bool myCompare(int val1 , int val2)
{return val1 val2;
}//反转和排序
void test01()
{listint L;L.push_back(90);L.push_back(30);L.push_back(20);L.push_back(70);printList(L);//反转容器的元素L.reverse();printList(L);//排序L.sort(); //默认的排序规则 从小到大printList(L);L.sort(myCompare); //指定规则从大到小myCompare是一个回调函数printList(L);
}int main() {test01();system(pause);return 0;
}set基本概念
简介
所有元素都会在插入时自动被排序
本质
set/multiset属于关联式容器底层结构是用二叉树实现。
set和multiset区别
set不允许容器中有重复的元素multiset允许容器中有重复的元素
set构造和赋值
功能描述创建set容器以及赋值
构造
setT st; //默认构造函数set(const set st); //拷贝构造函数
赋值
set operator(const set st); //重载等号操作符
插入
只有insert()
并且元素在插入之后会自动排序
大小和交换
功能描述
统计set容器大小以及交换set容器
函数原型
size(); //返回容器中元素的数目empty(); //判断容器是否为空swap(st); //交换两个集合容器
set插入和删除
功能描述
set容器进行插入数据和删除数据
函数原型
insert(elem); //在容器中插入元素。clear(); //清除所有元素erase(pos); //删除pos迭代器所指的元素返回下一个元素的迭代器。erase(beg, end); //删除区间[beg,end)的所有元素 返回下一个元素的迭代器。erase(elem); //删除容器中值为elem的元素。
set查找和统计
功能描述
对set容器进行查找数据以及统计数据
函数原型
find(key); //查找key是否存在,若存在返回该键的元素的迭代器若不存在返回set.end();count(key); //统计key的元素个数
set和multiset区别
学习目标
掌握set和multiset的区别
区别
set不可以插入重复数据而multiset可以set插入数据的同时会返回插入结果表示插入是否成功multiset不会检测数据因此可以插入重复数据
#include iostreamusing namespace std;int main()
{setint s;pairsetint::iterator,bool ret s.insert(10);if(ret.second){cout第一次插入成功endl;}else{cout第一次插入失败endl;}ret s.insert(10);if(ret.second){cout第二次插入成功endl;}else{cout第二次插入失败endl;}multisetint ms;ret ms.insert(10);if(ret.second){coutmultiset插入成功endl;}else{coutmultiset插入失败endl;}ret ms.insert(10);ret ms.insert(10);coutret.secondendl;coutret.secondendl;
}pair的使用
pair是成对存在的一组数据
对组的创建
pairtype1,type2 p(value1,value2);pairtype1,type2 p make_pair(value1,value2);
访问
p.firstp.second
set容器排序规则
默认排序规则从小到大利用仿函数改变规则
使用仿函数
#include iostream
#include set
using namespace std;
class MyCompare{
public:bool operator()(const intv1,const intv2){return v1v2;}
};
int main(){setint,MyCompare s;s.insert(1);s.insert(2);s.insert(3);for(const autoi:s){coutiendl;}
}set容器使用自定义数据类型插入时会报错所以需要指定排序规则
#include iostream
#include set
#include utilityusing namespace std;
class Person{
public:Person(string name,int age){this-name std::move(name);this-ageage;}string name;int age;
};
class comparePerson{
public:bool operator()(const Person p1, const Person p2){return p1.agep2.age;}
};int main(){setPerson,comparePerson s;s.insert(Person(刘备,12));s.insert(Person(关羽,21));for(const autoi:s){cout姓名:i.name 年龄:i.ageendl;}
}map容器
map基本概念
简介
map中所有元素都是pairpair中第一个元素为key键值起到索引作用第二个元素为value实值所有元素都会根据元素的键值自动排序
本质
map/multimap属于关联式容器底层结构是用二叉树实现。
优点
可以根据key值快速找到value值
map和multimap区别
map不允许容器中有重复key值元素multimap允许容器中有重复key值元素
map构造和赋值
功能描述
对map容器进行构造和赋值操作
函数原型
构造
mapT1, T2 mp; //map默认构造函数:map(const map mp); //拷贝构造函数
赋值
map operator(const map mp); //重载等号操作符
int main(){mapint,int mp;map.insert(pairint,int(1,10));map.insert(pairint,int(3,30));map.insert(pairint,int(2,20));for(const auto i:mp){coutkey值i.firstvaluei.secondendl;}
}map大小和交换
统计map容器大小以及交换map容器
函数原型
size(); //返回容器中元素的数目empty(); //判断容器是否为空swap(st); //交换两个集合容器
map插入和删除
功能描述
map容器进行插入数据和删除数据
函数原型
insert(elem); //在容器中插入元素。clear(); //清除所有元素erase(pos); //删除pos迭代器所指的元素返回下一个元素的迭代器。erase(beg, end); //删除区间[beg,end)的所有元素 返回下一个元素的迭代器。erase(key); //删除容器中值为key的元素。
map查找和统计
功能描述
对map容器进行查找数据以及统计数据
函数原型
find(key); //查找key是否存在,若存在返回该键的元素的迭代器若不存在返回set.end();count(key); //统计key的元素个数
map容器排序
学习目标
map容器默认排序规则为 按照key值进行 从小到大排序掌握如何改变排序规则
主要技术点:
利用仿函数可以改变排序规则
#include iostream
#include map
using namespace std;
class MyCompare{public:bool operator()(const intv1,const intv2){return v1v2;}
}test01(){mapint,int,MyCompare mp;mp.insert(pairint,int(1,10));mp.insert(pairint,int(4,40));mp.insert(make_pair(2,20));mp.insert(make_pair(3,30));for(const autoi:mp){coutkeyi.firstvaluei.secondendl;}
}
int main(){test01();
}函数对象
概念
重载函数调用操作符的类其对象常称为函数对象函数对象使用重载的()时行为类似函数调用也叫仿函数
本质
函数对象(仿函数)是一个类不是一个函数
函数对象在使用的时候可以像普通函数那样调用可以有参数可以有返回值函数对象可以拥有自己的状态
class MyAdd{
public:int operator()(int v1,int v2){status;return v1v2;}int status;
};一元谓词和二元谓词
返回bool类型的仿函数称为谓词如果operator()接收一个参数那么叫做一元谓词如果operator()接收两个参数那么叫做二元谓词
#include iostream
#include vector
#include algorithm
using namespace std;class MyCompare{
public:bool operator()(const intv1,const intv2){return v1v2;}
};
bool mycompare(const int v1,const intv2){return v1v2;
}int main(){vectorint v;v.push_back(10);v.push_back(20);v.push_back(30);v.push_back(40);v.push_back(50);sort(v.begin(),v.end(), MyCompare());//使用放函数的形式sort(v.begin(),v.end(), mycompare);//也可以不使用仿函数直接使用bool类型的函数for(const auto i:v){coutiendl;//遍历容器输出结果}
}STL内建函数对象
分类
算数仿函数关系仿函数逻辑仿函数 这些仿函数所产生对象的用法和一般函数一样 使用内建函数对象需要引入头文件#include functional 算数仿函数
功能描述
实现四则运算其中negate是一元运算其他都是二元运算
仿函数原型
templateclass T T plusT //加法仿函数templateclass T T minusT //减法仿函数templateclass T T multipliesT //乘法仿函数templateclass T T dividesT //除法仿函数templateclass T T modulusT //取模仿函数templateclass T T negateT //取反仿函数
void test01(){negateintn;coutn(50)endl;//-50plusintp;coutp(10,40)endl;//50
}关系仿函数
功能描述
实现关系对比
仿函数原型
templateclass T bool equal_toT //等于templateclass T bool not_equal_toT //不等于templateclass T bool greaterT //大于templateclass T bool greater_equalT //大于等于templateclass T bool lessT //小于templateclass T bool less_equalT //小于等于
void test01(){vectorint v;v.push_back(10);v.push_back(20);v.push_back(30);v.push_back(40);v.push_back(50);sort(v.begin(),v.end(),greaterint());
}逻辑仿函数
功能描述
实现逻辑运算
函数原型
templateclass T bool logical_andT //逻辑与templateclass T bool logical_orT //逻辑或templateclass T bool logical_notT //逻辑非
#include vector
#include functional
#include iostream
#include algorithm
using namespace std;
void test01()
{vectorint v;//如果类型为int型则除0以外的都是truev.push_back(true);v.push_back(false);v.push_back(true);v.push_back(3);for (auto it : v){cout it ;}cout endl;//逻辑非 将v容器搬运到v2中并执行逻辑非运算vectorint v2;v2.resize(v.size());transform(v.begin(), v.end(), v2.begin(), logical_not());for (auto it : v2){cout it ;}cout endl;
}int main() {test01();system(pause);return 0;
}