注*:以下笔记内容根据黑马程序员教程所做,传至线上仅为方便实时查阅,因个人疏忽、学艺不精或可导致笔记内容有误,望请阅览者不吝指出*
C++学习笔记
文章目录
1、基础语法入门
1.1初识
1、main是一个程序的入口,每个程序必有且只有一个
输出语法(print): cout<< " "<<endl;
快捷注释:ctrl+k+v
取消注释:ctrl+k+u
2、变量
创建语法:数据类型 变量名 = 变量初始值;
int a = 10;
3、常量——记录程序中不可更改的数据
2种方式定义:1、#define 常量名 常量值——宏常量
2、const 数据类型 常量名 = 常量值
4、关键字——预先保留的单词(标识符)
5、标识符命名规则
组成:字母、数字、下划线
第一个字符必须为字母或下划线
字母区分大小写
1.2 数据类型
1、整数型
short 短整形(-32768-32767)
int 整形 (-231~231-1)——最常用
long 长整形 (-231~231-1)
lon long长长整形(-263~263-1)
2、sizeof关键字——统计数据类型所占内存大小
语法:sizeof(数据类型/变量)
2、实型(浮点型)
单精度float 7位有效数字
双精度double 15-16位有效数字
语法: float 变量名 = 变量值f
科学计数法:float f2=3e2——3*10^2
float f2=3e-2——3*0.1^2
3、字符型
语法:char ch=‘a’; ——单引号内只能有一个字符,不可以识字符串
如何查看字符型变量对于ASCII编码
cout << (int)ch << endl;
a——97;A——65
4、转义字符——用于表示不能显示出来的ASCII码
\n 换行
\t 水平制表,空格,可以整齐输出数据
\\ 反斜杠
5、字符串型——用于表示一串字符
语法:
char 变量名[] = “字符串值”
string 变量名= “字符串值” 注:需在文件上方写明#include_2019版本可不用头文件
6、布尔类型bool(真/假)
创建:bool 变量名=true(打印值为1)
7、数据的输入——用于从键盘获取数据
语法:cin>>变量
1.3 运算符
1、算数运算符
+ | 把两个操作数相加 | A + B 将得到 30 |
---|---|---|
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
– | 自减运算符,整数值减少 1 | A-- 将得到 9 |
注:两个小数不可做取模运算
自增:后置自增A++——A-+、前置自增++A——A+1
自减:前置自减A–、后置自减–A
前置与后置区别:
前置——先让变量+1,后进行表达式运算
eg. a=10
b=++a*10
a=11,b=110
后置——先进行表达式运算,后让变量+1
eg. a=10
b=a++*10
a=11,b=100
2、赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
3、比较运算符
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。/等于 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。/不等于 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
4、逻辑运算符
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
注:在c++中,数值除了0都为真
1.4 程序流程结构
1、选择结构
1)if语句
单行if语句:if(条件)
{
执行
}
多行if语句
if(条件)
{满足执行的语句}
else
{不满足执行的语句}
多条件if语句
if(条件1)
{执行满足条件1的语句}
else if(条件2)
{执行满足条件2的语句}
else
{执行都不满足的语句}
嵌套if语句
if(条件1)
{if(条件2)
{执行}
else
{执行}}
else if(条件2)
{执行满足条件2的语句}
else
{执行都不满足的语句}
2)switch语句
switch(表达式)
{
case 结果1:执行语句;break;
case 结果2:执行语句;break;
…
default:执行语句; break;
}
比较适用判断整数型分布或字符型分布
if与switch语句区别:
后者缺点,不能判断区间,只能是整型或者字符型
后者优点,结构清晰,执行效率高
2、循环结构
1)while
语法:while(条件){循环语句}
2)do…while语句
语法:do{循环语句} while{循环条件}
与while区别:do…while会先执行一次循环语句,再判断循环条件
- for循环
语法:for(起始表达式0;条件表达式1;末尾循环体3){循环语句2;}
执行步骤:0123123123…
4)嵌套循环
3、跳转结构
1)break语句
使用时机:switch循环、一般循环、嵌套循环
2)continue语句
作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环
3)goto语句——一般不推荐使用
作用:可以无条件跳转语句
语法:goto 标记
解释:如果标记的名称存在,则跳转至有标记的地方
4、三目运算符
- 三目运算符:一定要用于赋值语句。
- 三目运算表达式:<表达式1>?<表达式2>:<表达式3>
注:”?”运算符的含义是: 先求表达式1的值,如果为真,则执行表达式2,并返回表达式2的结果 ; 如果表达式1的值为假,则执行表达式3 ,并返回表达式3的结果。****
1.5 数组
1、一维数组
定义方式:
1)数据类型 数组名[数组长度];
2)数据类型 数组名[数组长度]={值1,值2…};
3)数据类型 数组名[]={值1,值2…};
注:下标从0开始索引
初始化数据时,没有填写值则用0进行填补
2、一维数组数组名
用途:统计数组长度 sizeof (数组名);
获取数组首地址 cout<<数组名<<endl;
cout<<(int)数组名<<endl;十进制查看方式
获取数组中第一个元素首地址 cout<<(int)&数组名[0]<<endl;
计算数组元素个数:sizeof (数组名) / sizeof (数组名[0])
3、冒泡排序——常用排序法,对数组内元素进行排序
1)比较相邻的元素,若第一个比第二个大,则交换
2)对每一个相邻元素做同样的工作,执行完毕,找到第一个最大值
3)重复以上步骤,每次比较次数-1,知道不需要比较
注:排序总轮数=元素个数-1
没轮对比次数=元素个数-排序轮数-1
4、二维数组定义方式
1)数据类型 数组名 [行数] [列数]
2)数据类型 数组名 [行数] [列数]={{数据1,数据2},{数据3,数据4}};
3)数据类型 数组名 [行数] [列数]={数据1,数据2},{数据3,数据4};
4)数据类型 数组名 [][列数] [] [列数]={数据1,数据2,数据3,数据4};
注:索引从0开始
5、二维数组名
用途:查看二维数组所占内存空间
获取二维数组首地址
获取数组中第一行占用内存 cout << sizeof(数组名[0]) << endl;
获取数组中第一个元素占用内存 cout << sizeof(数组名[0] [0]) << endl;
行数计算:sizeof(数组名) / sizeof(数组名[0])
列数计算:sizeof(数组名) / sizeof(数组名[0] [0])
1.6 函数
1、定义
定义步骤:1)返回值类型;2)函数名;3)参数列表;4)函数体语句;5)return表达式
语法
返回值类型 函数名 (参数列表)
{
函数体语句
return表达式
}
注:如果函数不需要返回值,申明的时候写void,可以不写return
2、调用
语法:函数名(参数)
3、值传递——实参将数值传给形参
形参发生改变,不会影响实参
4、函数的常见样式
1)无参无返
2)有参无返
3)无参有返
4)有参有返
5、函数的声明
作用:告诉编译器函数名称以及如何调用函数,函数的实际主题可以单独定义
注:函数的声明可以多次,但是函数的定义只能有一次
6、函数的分文件编写——让代码结构更清晰
4个步骤:1)创建后缀名为.h的文件
2)创建后缀名为.cpp的源文件
3)在头文件写函数的申明(写上框架)
4)在源文件写函数的定义(写上头文件)
1.7 指针
1、基本概念
作用:通过指针间接访问内存
注:1)内存编号是从0 开始记录的,一般用十六进制数字表示
2)可以利用指针变量保存地址
2、定义和使用
定义语法:数据类型 *指针变量名:
指针记录变量地址语法:指针变量名=& 变量名
使用指针:
*指针变量名 = 值
注:*代表解引用,找到指针指向的内存中的数据
3、指针所占内存空间
在32位操作系统下,指针是占4个字节空间大小,无论数据类型
在64位操作系统下,指针是占4个字节空间大小
4、空指针和野指针
1)空指针——指针变量指向内存中编号为0的空间
用途:初始化指针变量
注:空指针指向的内存是不可以访问的
2)野指针——指针变量指向非法的内存空间
注:在程序中尽量避免野指针
空指针和野指针都不是我们申请的空间,因此不要访问
5、const修饰指针
const修饰指针——常量指针
eg. const int * p = & a
特点:指针的指向可以修改,但是指针指向的值不可更改
修改指向:p = & b
const修饰常量——指针常量
eg. int * const p = & a
特点:指针的指向不可以修改,但是指针指向的值可更改
修改值:*p = 20
const既修饰指针,又修饰常量
eg. const int * const p = & a
特点:指针的指向和指向的值都不可以修改
6、指针和数组
目的:利用指针访问数组元素
7、指针和函数
作用:利用指针作函数参数,可以修改实参的值
不同于值传递
8、指针、数组、函数
案例:封装一个函数,利用冒泡排序,实现对整数型升序排序
9、复杂指针
-
数组指针(行指针)——指向数组的指针
int (*p)[n];
解析:()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长,也就是说执行p+1时,p要跨过n个整型数据的长度
-
指针数组
int *p[n];
解析:[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素(数组的每一个元素都是一个指针)。这里执行p+1时,则p指向下一个数组元素
-
函数指针——指向函数的指针变量,本身是指针,指针变量指向函数
有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数
格式: 返回值类型 (*指针变量名)([形参列表]);
复杂函数指针识别:
int (*func)(int *p);
func的外面有一对圆括号,而且左边是一个号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int类型的形参,返回值类型是int。
int (*func[5])(int *p);
func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比高,func先跟[]结合,因此修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int类型的形参,返回值类型为int
int (*(*func)[5])(int *p);
func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int形参,返回值为int类型的函数。
int (*(*func)(int *p))[5];
func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。
-
指针函数——返回值为指针的函数,本质是一个函数,返回的是某一类型的一个指针
语法: 类型名 *函数名(函数参数列表)
解析:后缀运算符括号“()”表示这是一个函数,其前缀运算符星号“*”表示此函数为指针型函数,其函数返回值为指针,即它带回来的值的类型为指针,当调用这个函数后,将得到一个“指向返回值为…的指针(地址)。
“类型名”表示函数返回的指针指向的类型”。
注:“(函数参数列表)”中的括号为函数调用运算符,在调用语句中,即使函数不带参数,其参数表的一对括号也不能省略
*int pfun(int, int);
pfun是一个函数,接着再和前面的“*”结合,说明这个函数的返回值是一个指针。由于前面还有一个int,也就是说,pfun是一个返回值为整型指针的函数。
1.8、结构体
1、基本概念
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
2、结构体定义和使用
语法: struct 结构体名 {结构体成员列表}
创建方式:
- struct 结构体名 变量名(此处的struct可省略)
- struct 结构体名 变量名={成员1值,成员2值…}
- 定义结构体时顺便创建变量
3、结构体数组
作用:将定义的结构体放入到数组中方便维护
语法:struct 结构体名 数组名 [元素个数] = {{},{},…}
4、结构体指针
- 利用操作符“->”可以通过结构体指针访问结构体属性
5、结构体嵌套结构体
6、结构体做函数参数
作用:将结构体作为参数向函数中传递
传递方式:
- 值传递——不可修改主函数中的数据
- 地址传递——可修改主函数中的数据
7、结构体中const使用常见
作用:防止误操作
8、结构体案例
随机数种子
scrand( (unsigned int) time (NULL));
2、核心编辑——面向对象编程
2.1、内存分区模型
程序在执行时,会划分4个区域:
-
代码区:存放函数体的二进制代码,由操作系统进行管理
特点:共享、只读
-
全局区:存放全局变量和静态变量以及常量,由系统释放
全局变量——创建在main函数上方的变量
静态变量——在普通局部变量前加static
常量:
- 字符串常量 eg.“hello”
- const修饰的变量 :
- const修饰的全局变量(一般命名方式:c_g_变量名)
- const修饰的局部变量——局部常量(一般命名方式:c_l_变量名)——放在栈区
-
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
局部变量——创建在函数内的变量
注:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
形参数据也放在栈区
-
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
利用new创建堆区数据,用指针(局部变量,在栈区)接收堆区数据的地址
new操作符
堆区开辟数据,手动开辟,手动释放,释放利用操作符delete
语法:new 类型 (初值) /new 类型 [大小]
利用new创建的数据,会返回该数据对应的类型的指针
基本用法:
int *a = new int[5];//仅仅分配了空间
class A {...} //声明一个类 A
A *obj = new A(); //使用 new 创建对象
delete []a;
delete obj;
new int;
//开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100);
//开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
new char[10];
//开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4];
//开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
float *p=new float (3.14159);
//开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
功能:1)分配空间 2)调用构造函数
2.2 引用
1、基本使用
作用:给变量起别名
语法:数据类型 &别名=原名
2、注意事项
-
引用必须初始化
eg. int a=10;
int &b=a;//初始化
-
引用在初始化后,不可以改变
eg. int c=20;
int &b=c; //错误操作
b=c; //可以,是赋值操作,不是更改引用;运行后a,b,c=20
3、引用做函数参数
作用:函数作参数是,利用引用技术让形参修饰实参
优点:可以简化指针修改实参
注:别名和原名可以一致
4、引用做函数返回值
作用:引用可以作为函数的返回值存在
注:不要返回局部变量引用
用法:函数调用作为左值
5、引用的本质——指针常量
int & ref=a 等价于 int * const ref = & a;
6、常量引用
作用:修饰形参,防止误操作
在函数形参列表中,加const修饰形参,防止形参改变实参
2.3 函数提高
1、函数默认参数
——将函数中的形参设定为默认值
语法: 返回值类型 函数名 (参数=默认值) {}
**注:**1、如果某个位置有默认参数,那么从这个位置往后,从左到右都必须有默认值;即默认了b,则c也需要设置默认
2、如果函数声明有默认参数,函数实现就不能有默认参数;即声明和实现只能由一个有默认参数
2、函数占位参数
——函数的形参列表中可以由占位参数,用来做占位,调用函数时必须填补该位置
语法: 返回值类型 函数名(数据类型){}
占用参数可以有默认参数
语法: 返回值类型 函数名(数据类型 = 默认值){}
3、函数重载
1)概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者个数不同 或者顺序不同
**注:**函数的返回值不可以作为函数重载的条件
2)注意事项
- 引用可作为重载条件
- 函数重载碰到默认参数,会出错,应避免
2.4 类和对象
C++面向对象三大特性:封装、继承、多态
万事万物皆为对象,对象上有其属性和行为
具有相同性质的对象,抽象为类
1、封装
基础知识
意义:
- 属性和行为作为一个整体
- 将属性和行为加以权限控制
语法:class 类名 {访问权限:属性/行为};
行为通常用函数
实例化——通过一个类,创建一个对象的过程
类中的属性和行为,统一称为成员
属性 成员属性 成员变量
行为 成员函数 成员方法
访问权限
- public 公共权限 —— 类内类外够可访问
- protected 保护权限 —— 类内可访问,类外不可访问,子类也可访问父类保护内容
- private 私有权限 —— 类内可访问,类外不可访问, 子类不可访问父亲私有内容
struct和class区别
- struct默认权限为公共
- class默认权限为私有
成员属性私有化
设置为private 优点:可以自己控制读写权限; 对于写权限,可以检测数据的有效性
#include<iostream>;
using namespace std;
#include<string>;
const double pi = 3.14;
//创建圆类
class circle
{
//访问权限
public: //公共权限
//属性
int m_r;
//行为
double cal()
{
return 2 * pi * m_r;
}
};
//练习——创建学生类
class Student
{
public:
//属性
string name;
string id;
//行为
void show()
{
cout << "姓名: " << name << " id: " << id << endl;
}
//给姓名赋值
void setName(string s_name)
{
name = s_name;
}
void setID(string s_id)
{
id = s_id;
}
};
//成员属性私有化
class person
{
public:
//设置姓名
void setName(string myname)
{
name = myname;
}
//获取姓名
string getname()
{
return name;
}
//获取年龄 当设置年龄可读可写 且对输入年龄有一定限制——检测数据有效性
int getage()
{
//age = 0;//初始化
return age;
}
void setage(int myage)
{
if(myage>100||myage<0)
{
cout << "年龄有误" << endl;
return;
}
age = myage;
}
//设置情人,只写
void setlover(string mylover)
{
lover = mylover;
}
private:
string name;//设置可读可写
int age;//只读
string lover;//只写
};
int main()
{
//通过圆类,创建对象
circle c1; //实例化——通过一个类,创建一个对象的过程
//给对象属性进行赋值
c1.m_r = 10;
cout << "返回周长= " << c1.cal() << endl;
Student s1;
//s1.id = "20184229015";
//s1.name = "YQ";//第一种赋值方式
s1.setName("YQ");//第二种赋值方式
s1.setID("20184229015");
s1.show();
/*cout << "学生学号: " << s1.id << " 学生姓名: " << s1.name << endl;*/
person p1;
p1.setName("张三");
cout << "姓名: " << p1.getname() << endl;
p1.setage(20);
cout << "年龄: " << p1.getage() << endl;
p1.setlover("CJ");
return 0;
}
2、对象特性——对象的初始化和清理
-
构造函数和析构函数
——用于解决对象的初始化和清理问题
如果我们不提供构造和析构,编译器会提供;编译器提供的构造函数和析构函数是空实现
构造函数——主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数——主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
1)没有返回值也不写void
2)函数名称与类名相同
3)构造函数可以有参数,因此可以发生重载
4)在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
1)没有返回值也不写void
2)函数名称与类名相同,在名称前加上符号~
3)析构函数不可以有参数,因此不可以发生重载
4)在调用对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
-
构造函数的分类及调用
分类:
- 按参数分:有参构造和无参构造(默认构造)
- 按类型分:普通构造和拷贝构造
调用方式:
- 括号法
- 显示法
- 隐式转换法
注意事项:
- 调用默认构造函数时,不要加(),因为编译器会认为是一个函数的声明,不会认为在创建对象
- 不要利用拷贝构造函数初始化匿名对象,编译器会认为person(p)==person p;在创建对象
-
拷贝构造函数调用时机
- 用一个已经创建完毕的对象初始化一个新对象,即将老对象复制给新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
-
构造函数调用规则
默认情况下,C++编译器至少个一个类添加3个函数
- 默认构造函数,函数体为空
- 默认析构函数,函数体为空
- 默认拷贝构造函数,对属性进行值拷贝
调用规则如下:
- 用户定义有参构造函数,C++不再提供默认无参构造,但是会提供拷贝构造
- 用户定义拷贝构造函数,C++不再提供其它构造函数
-
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
- 存在问题,堆区的内存重复释放,因此需要自己实现拷贝构造函数,解决浅拷贝带来的问题
深拷贝:在堆区重新申请空间,进行拷贝操作
注:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
#include<iostream>;
using namespace std;
class person
{
public:
//(普通)构造函数
person()
{
cout << "person构造函数的调用" << endl;
}
//有参构造函数
person(int a)
{
age = a;
cout << "person有参构造函数的调用" << endl;
}
//拷贝构造函数,将p对象的所有属性拷贝到person上,
person(const person& p)//加const限定p不能修改,用引用的方式传递
{
cout << "person拷贝构造函数的调用" << endl;
age = p.age;//将传人人身上的所有属性,拷贝到自身身上
}
//析构函数
~person()
{
cout << "person析构函数的调用" << endl;//随着对象p的释放,析构函数被调用
}
int age;//属性
};
void test01()
{
person p;//p在栈区,test01执行完毕后,释放这个对象
}
//构造函数的调用
void test02()
{
//1、括号法
person p;//默认构造函数调用
person p1(10);//有参构造函数调用
person p2(p1);//拷贝构造函数调用
//注:调用默认构造函数时,不要加(),因为编译器会认为是一个函数的声明,不会认为在创建对象
cout << "p1的年龄:"<<p1.age << endl;
cout << "p2的年龄:" << p2.age << endl;
//2、显式法
person p3 = person(10);
person p4 = person(p3);
//左侧的如person(10)称为匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象
//注:不要利用拷贝构造函数初始化匿名对象,编译器会认为person(p3)==person p3;在创建对象,是对象申明
//3、隐式转换法
person p5 = 10;
person p6 = p5;
}
//拷贝构造函数的调用时机
class people
{
public:
people()
{
cout << "people默认构造函数的调用" << endl;
}
people(int age,int height)
{
m_age = age;
m_height =new int(height);//创建在堆区,用指针接收
cout << "people有参构造函数的调用" << endl;
}
people(const people & m)
{
m_age = m.m_age;//浅拷贝操作
//m _ height = m.m_height;编译器会默认实现这行代码
//深拷贝操作
m_height = new int(*m.m_height);
cout << "people拷贝构造函数的调用" << endl;
}
~people()
{
//析构代码,将堆区开辟数据进行释放操作
if(m_height!=NULL)
{
delete m_height;
m_height = NULL;//防止野指针的出现
}
cout << "people析构函数的调用" << endl;
}
int m_age;
int* m_height;
};
//用一个已经创建完毕的对象初始化一个新对象
void test03()
{
people m1(20,160);
cout << "m1的年龄:" <<m1.m_age<<" m1的身高:"<< *m1.m_height<<endl;//加*是为了解引用
people m2(m1);
cout << "m2的年龄:" << m2.m_age << " m2的身高:" << *m2.m_height << endl;
}
//值传递的方式给函数参数传值 值传递的本质就是拷贝
void doWork(people m)
{
}
void test04()
{
people m;
doWork(m);//调用拷贝构造函数
}
//以值方式返回局部对象
people doWork2()
{
people m3;
cout << (int*)&m3<< endl;
return m3;//此处返回的不是m3原对象,而是重新copy的一个对象
}
void test05()
{
people m = doWork2();//调用拷贝构造函数
cout << (int*)&m << endl;
}
int main()
{
//test01();//返回结果为person类下的person构造函数结果;在调用对象时会自动调用构造,无须手动调用,而且只会调用一次
test02();//
//test03();
//test04();
//test05();
return 0;
}
-
初始化列表
——用于初始化属性
语法:构造函数():属性1(值1),属性2(值2)…{}
-
类对象作为类成员
类中的成员可以是另一个类的对象,称为对象成员,如phone
- 构造顺序:当其它类的对象作为本类的成员,构造时候先构造类对象,再构造自身(练习中先构造phone,再构造person)
- 析构顺序:先析构自身,再析构类对象(先析构person,再析构phone)
#include<iostream>;
using namespace std;
class phone
{
public:
//phone m_phone=pname
phone(string pname)
{
m_pname = pname;
cout << "phone的构造函数调用" << endl;
}
~phone()
{
cout << "phone的析构函数调用" << endl;
}
string m_pname;
};
class person
{
public:
person(string name,string pname):m_name(name),m_phone(pname)
{
cout << "person的构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
string m_name;
phone m_phone;
};
void test01()
{
person p("张三", "iPhone");
cout << p.m_name << "拿着:" << p.m_phone.m_pname << endl;
}
int main()
{
test01();
return 0;
}
-
静态成员
——成员变量和成员函数前加上关键字static,称为静态成员
-
静态成员变量
-
所有对象共享同一份数据
-
在编译阶段分配内存——程序没运行前就分配内存,在全局区
-
类内声明,类外初始化
声明:static int a; static 变量类型 变量名
初始化: int person :: a=100; 变量类型 类名 ::变量名=变量值
注:静态成员变量不属于某个对象,所有对象共享同一份数据,因此有两种访问方式
- 通过对象进行访问 对象.变量名
- 通过类名进行访问 类名::变量名
注:静态成员变量有访问权限,若声明时加上private,则不可访问
-
-
静态成员函数
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
两种访问方式:
-
通过对象进行访问
对象名.函数名()注:要先创建对象
-
通过类名进行访问 类名::函数名()
注:静态成员函数有访问权限
-
-
-
3、C++对象模型和this指针
-
成员变量和成员函数分开存储
空对象的内存空间为1字节——即每个空对象也应该有一个独一无二的内存地址
注:只有非静态成员变量才属于类的对象上
静态成员变量、非静态成员函数、静态成员函数不属于类对象
-
this指针概念——本质是指针常量
作用:当有多个对象调用同一个函数(非静态成员函数),this指针用于区分是哪一个对象调用自己
特性:this指针指向被调用的成员函数所属的对象
用途:
- 当形参和成员变量重名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
-
空指针可以访问成员函数
-
const修饰成员函数——称为常函数(成员函数加const)
**常**代表函数有些属性限定为只读状态
常函数:
- 不可以修改成员属性
- 成员属性声明加关键字mutable后,在常函数中仍然可以修改
常对象:
- 声明对象前加const
- 常对象只能调用常函数
4、友元
作用:让一个函数或者类访问另一个类(本类)中私有成员
关键字:friend
三种实现:
-
全局函数做友元
语法:friend 全局函数申明 ——写在本类内
-
类做友元
语法:friend 类申明 ——写在本类内
-
成员函数做友元
语法:friend 成员函数申明 ——写在本类内
注:本类创建应在另一个类后面,eg. 先创建gGay,再创建building类
#include<iostream>;
using namespace std;
class building
{
//全局函数做友元,可以访问私有成员
friend void goodGay(building* b);
//gay类是本类的好友
friend class Gay;
public:
//building()//构造函数,进行赋值 用于全局函数做友元
//{
// m_Sittingroom = "客厅";
// m_bedroom = "卧室";
//}
building();//用于类友元,改变创建方式,在类外创建函数
public:
string m_Sittingroom;
private:
string m_bedroom;
};
//如何类外创建Building类里的building函数
building::building()
{
m_Sittingroom = "客厅";
m_bedroom = "卧室";
}
//全局函数
void goodGay(building* b)
{
cout << "好基友全局函数 正在访问:" << b->m_Sittingroom << endl;
cout << "好基友全局函数 正在访问:" << b->m_bedroom << endl;
}
//成员函数做友元
class build;
class gGay
{
public:
gGay();
void vis();//让vis函数可以访问building中私有成员
void vis2();//让vis2函数不可以访问building中私有成员
build* b;
};
class build
{
//gGay类下的vis函数是本类的好友
friend void gGay::vis();
public:
build();
public:
string m_Sittingroom;
private:
string m_bedroom;
};
gGay::gGay()
{
b = new build;
}
build::build()
{
m_Sittingroom = "客厅";
m_bedroom = "卧室";
}
void gGay::vis()
{
cout << "vis函数正在访问:" << b->m_Sittingroom << endl;
cout << "vis函数正在访问:" << b->m_bedroom << endl;
}
void gGay::vis2()
{
cout << "vis2函数正在访问:" << b->m_Sittingroom << endl;
//cout << "vis函数正在访问:" << b->m_bedroom << endl;
}
//类做友元
class Gay
{
public:
Gay();//创建类外Gay函数
void visit();//参观函数 访问building中的属性
building* b1;//创建指针
};
Gay::Gay()
{
//作用,让指针创建建筑物对象
b1 = new building;//new building相当于在堆区创建对象
}
//如何实现gay类里的visit参观函数
void Gay::visit()
{
cout << "好基友类 正在访问:" << b1->m_Sittingroom << endl;
cout << "好基友类 正在访问:" << b1->m_bedroom << endl;
}
//用于全局函数友元测试
void test01()
{
building b;
goodGay(&b);//&b指传入b的地址,因为b是指针
}
//用于类元测试
void test02()
{
Gay gg;//创建gg对象,调用构造函数——即创建building对象,调用building构造函数——属性进行赋值
gg.visit();
}
//用于成员函数友元测试
void test03()
{
gGay gay;
gay.vis();
gay.vis2();
}
int main()
{
//test01();
//test02();
test03();
return 0;
}
5、运算符重载
——对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OhZSszAM-1616402185504)(D:\研究生相关文件\软件学习\C++学习\图库\运算符号重载解释.JPG)]
-
加号运算符重载 +
——实现两个自定义数据类型相加的运算
注:对于内置的数据类型表达式的运算符是不可能改变的,eg. int a+ int b
不要滥用运算符重载
重载方法
- 全局函数重载
- 成员函数重载
-
左移运算符重载 <<
——可以输出自定义数据类型
只能用全局函数重载
注:重载左运算符可自定义输出数据类型
-
**递增运算符重载 **++
——实现自己的整型数据
-
前置递增——返回的是引用,且需加int做占位符
-
后置递增——返回的是值
重载方法:成员函数重载和全局函数重载结合
-
-
**赋值运算符重载 ** =
c++编译器除了自动添加构造函数、析构函数、拷贝函数,还有赋值运算符operator=函数,对属性进行值拷贝
注:如果类中有属性指向堆区,也会出现深浅拷贝的问题
重载方法:成员函数重载
-
关系运算符重载 >、<、=
——让两个自定义类型对象进行操作
重载方法:成员函数重载
-
函数调用运算符重载()
由于重载使用的方式想函数的调用,因此称为*仿函数*
仿函数没有固定写法,非常灵活
重载方法:成员函数重载
6、继承
-
1 基本语法与知识
优点:减少重复代码
语法:class 子类:继承方式 父类
子类——也叫派生类
派生类包含两部分:从基类继承过来的(共性); 自己增加的成员(个性)
父类——也叫基类
-
2 继承方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JsneKXL3-1616402185510)(D:\研究生相关文件\软件学习\C++学习\图库\继承方式说明.JPG)]
- 公共方式——public
- 保护继承——protected
- 私有继承——private
-
3 继承中的对象模型
question:从父类继承过来的成员,那些属于子类对象?
在父类中**所有**非静态成员属性都会被子类继承,父类中私有成员属性是被 编译器给隐藏了,但是能被继承
另一种查看子类继承属性的方式:
开始——VS——Developer command prompt vs 2019——(如需跳转盘符 “盘符:”)复制所在文件夹的路径——输入cd ,在命令符中复制路径 ,回车——输入dir, 回车——输入“cl /d1 reportSingleClassLayout类名 “文件名”,回车
-
4 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数,那么父类 和子类的构造和析构顺序谁先谁后?
结论:先构造父类,再构造子类
先析构子类,再析构父类
-
5 继承同名成员处理方式
question:当子类和父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?
-
访问子类同名成员 直接访问即可
语法:对象.属性
-
访问父类同名成员 需要加作用域
访问父类成员属性处理方式
语法:子类对象.父类名::属性
访问父类成员函数处理方式
语法:子类对象.父类名::函数名
注:子类和父类有同名成员函数,子类的同名成员函数会隐藏掉父类所有的同名成员函数(如重载)
-
-
6 继承同名静态成员处理方法
静态成员和非静态成员出现同名,处理方式一致:
-
访问子类同名成员 直接访问即可
-
访问父类同名成员 需要加作用域
静态成员属性:
-
通过对象访问
子类 语法:对象.属性
父类 语法:对象.父类名::属性
-
通过类名访问
子类 语法:子类名::属性
父类 语法:子类名::父类名::属性
-
注:静态成员函数调用方式同上
子类和父类有同名静态成员函数,子类的同名静态成员函数会隐藏掉父类所有的同名静态成员函数(如重载)
-
-
7 多继承语法
——允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式,父类2…
多继承可能会引发父类中有同名成员出现,需要加作用域
注:C++实际开发中不建议用多继承
-
8 菱形继承——钻石继承
引发的问题:二义性,数据两份
虚继承 加关键字 virtual
7 多态
-
1 基本概念
多态分为两类:
- 静态多态——如函数重载、运算符重载
- 动态多态——派生类和虚函数实现运行时多态
静态多态与动态多态区别:
- 静态多态的函数地址早绑定——编译阶段确定函数地址
- 动态多态的函数地址晚绑定——运行阶段确定函数地址
动态多态满足条件:
-
有继承关系
-
子类需重写父类的虚函数
重写——函数返回值类型、函数名、参数列表完全相同
动态多态使用:
父类的指针或者引用 指向子类对象
#include<iostream>;
using namespace std;
class animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class cat :public animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//地址早绑定,在编译阶段确定函数地址
void doSpeak(animal & animal)//父类引用可直接指向子类对象 animal & animal=cat
{
animal.speak();
}
//如果想执行让猫说话,地址则不能提前绑定,需在运行阶段绑定,在父类中speak函数前加virtual
void test01()
{
cat cat;
//doSpeak(cat);//加virtual之前动物在说话
doSpeak(cat);//小猫在说话
}
int main()
{
test01();
return 0;
}
- 2 多态的原理剖析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JDnkKasi-1616402185512)(D:\研究生相关文件\软件学习\C++学习\图库\多态原理剖析.JPG)]
案例——计算器类
优点:代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
#include<iostream>;
using namespace std;
//计算器案例
//普通写法
class calculator
{
public:
int getResult(string oper)
{
if (oper == "+")
{
return m_num1 + m_num2;
}
else if (oper == "-")
{
return m_num1 - m_num2;
}
else if (oper == "*")
{
return m_num1 * m_num2;
}
//如果想扩展新的功能,需要扩展源码(getResult)
}
int m_num1;
int m_num2;
};
void test01()
{
calculator c;
c.m_num1 = 10;
c.m_num2 = 5;
cout << c.m_num1 << "+" << c.m_num2 << "=" << c.getResult("+") << endl;
cout << c.m_num1 << "-" << c.m_num2 << "=" << c.getResult("-") << endl;
cout << c.m_num1 << "*" << c.m_num2 << "=" << c.getResult("*") << endl;
}
//多态写法
//计算器抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
//纯虚函数写法 virtual int getResult()=0;
int m_num1;
int m_num2;
};
//加法类
class AddCalculator:public AbstractCalculator
{
//如果用纯虚函数写法,子类应重写纯虚函数
//virtual int getResult(){return m_num1 + m_num2;};
int getResult()
{
return m_num1 + m_num2;
}
};
//减法类
class SubCalculator :public AbstractCalculator
{
int getResult()
{
return m_num1 - m_num2;
}
};
//乘法类
class MultiCalculator :public AbstractCalculator
{
int getResult()
{
return m_num1 * m_num2;
}
};
void test02()
{
//多态使用条件,父类指针或引用指向子类对象
//用指针方式
//加法运算
AbstractCalculator* c2 = new AddCalculator;
c2->m_num1 = 10;
c2->m_num2 = 5;
cout << "指针方式" << endl;
cout << c2->m_num1 << "+" << c2->m_num2 << "=" <<c2->getResult()<< endl;
//new开辟数据,手动销毁
delete c2;//只是销毁堆区数据,创建的父类指针还在
//减法运算
c2 = new SubCalculator;//改变指针指向
c2->m_num1 = 10;
c2->m_num2 = 5;
cout << c2->m_num1 << "-" << c2->m_num2 << "=" << c2->getResult() << endl;
delete c2;
//乘法运算
c2 = new MultiCalculator;
c2->m_num1 = 10;
c2->m_num2 = 5;
cout << c2->m_num1 << "*" << c2->m_num2 << "=" << c2->getResult() << endl;
delete c2;
}
int main()
{
//test01();
test02();
return 0;
}
-
3 纯虚函数和抽象类
由于在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
语法:virtual 返回值类型 函数名(参数列表)=0;
当类中有纯虚函数,则将这个类称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
-
4 虚析构和纯虚析构
在使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码进行释放
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
二者共性:
- 可以解决父类指针释放子类对象
- 都需要具体的函数(代码)实现
二者区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名()=0;——类内
类名::~类名{} ——类外
2.5 文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
进行文件操作,需要包含头
文件类型:
1)文本文件——以ASCII码存储
2)二进制文件——二进制存储,用户一般不能直接读懂它们
操作文件的三大类:
1)ofstream:写操作
2)ifstream:读操作
3)fstream:读写操作
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
1 文本文件
-
写文件
步骤:
- 包含头文件—— #include
- 创建流对象——ofrstream ofs;
- 打开文件—— ofstream(“文件路径”,打开方式)
- 写数据—— ofs<<“写入的数据”
- 关闭文件——ofs.close();
打开方式:
模式标志 描述 ios::app 追加模式。所有写入都追加到文件末尾。 ios::ate 文件打开后定位到文件末尾。 ios::in 打开文件用于读取。 ios::out 打开文件用于写入。 ios::trunc 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。(先删除,再创建) ios::binary 二进制方式
注:文件打开方式可以组合使用,利用|操作符
eg. 用二进制饭时写文件 ios::binary|ios::out
-
读文件
步骤:
- 包含头文件—— #include
- 创建流对象——ifrstream ifs;
- 打开文件并判断文件是否打开成功—— ifs.open(“文件路径”,打开方式)
- 读数据——4种方式
- 关闭文件——ifs.close();
2 二进制文件
指定打开方式ios::binary
-
写文件
——利用流对象调用成员函数write
函数原型:ostream write (const char * buffer, int len);
参数解释:字符指针buffer指向内存种一段存储空间,len是读写的字节数
-
读文件
——利用流对象调用成员函数read
函数原型:ostream read (char * buffer, int len);
参数解释:字符指针buffer指向内存种一段存储空间,len是读写的字节数
#include<iostream>;
using namespace std;
#include<fstream>;
#include<string>;
//文本文件 写文件
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "窗前明月光" << endl;
ofs << "疑是地上霜" << endl;
ofs.close();
}
//文本文件 读文件
void test02()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())//判断是否打开成功
{
cout << "文件打开失败" << endl;
return;
}
//读数据方式1
//char buf[1024] = { 0 };//buf——准备字符数组,1024是准备的字符数组长度
//while(ifs >> buf)
//{
// cout << buf << endl;
//}
//读数据方式2
/*char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}*/
//读数据方式3
string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}
//读数据方式4——不推荐使用
//一个字符一个字符地读取
//char c;
//while ((c = ifs.get()) != EOF)//EOF——end of file
//{
// cout << c;
//}
ifs.close();
}
//二进制文件 写文件
class person
{
public:
char m_name[64];//注:写文件时不要用string 字符数组
int m_age;
};
void test03()
{
ofstream ofs;
ofs.open("person.txt", ios::out | ios::binary);//也可以在上一步进行打开,即ofstream ofs("person.txt", ios::out | ios::binary);
person p = {"YQ",18};
ofs.write((const char*)&p, sizeof(person));
ofs.close();
}
//二进制文件 读文件
void test04()
{
ifstream ifs;
ifs.open("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
person p;
ifs.read((char*)&p, sizeof(person));
ifs.close();
}
int main()
{
//test01();
//test02();
//test03();
test04();
return 0;
};
3、提高编辑——泛型编程思想、STL
3.1 模板
1 概念
特点:不可以直接使用,只是一个框架
模板的通用并不是万能的
C++的另一种编程思想称为泛型编程,主要利用的技术就是模板
2种模板机制:函数模板和类模板
2 函数模板
作用:建立一个通用函数,不具体制定函数返回值类型和形参类型,用一个虚拟的类型来代表。
-
1 语法
template
函数声明或定义
解释:template——声明创建模板
typename——表明其后面的符号是一种数据类型,可以用class代替
T——通用的数据类型,名称可替换,通常为大写字母
2种使用方式:
- 自动类型推导
- 显示指定类型——建议使用
使用模板目的:提高代码复用性,将类型参数化
-
2 注意事项
- 使用自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
-
3 案例
——利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序,排序规则按从大到小,排序算法为选择排序,分别利用char和int数组进行测试
-
4 普通函数和函数模板区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
-
5 普通函数与函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模块可以产生更好的匹配,优先调用函数模板
注:实际开发中,提高函数模板,就不写普通函数
-
6 模板的局限性
- 通用性不是万能的
- 利用具体化的模板,可以解决自定义类型的通用化
- 学习模板不是为了写模板,而是在STL能够运用系统提供的模板
3 类模板
作用:建立一个通用类,类中的成员,数据类型可以不具体指定,用一个虚拟的类型来代表
- 1 语法
template
类
解释:
template——声明创建模板
typename——表明其后面的符号是一种数据类型,可以用class代替
T——通用的数据类型,名称可替换,通常为大写字母
-
2 类模板和函数模板区别
- 类模板没有自动类型推导的方式
- 在模板参数列表中可以有默认参数
-
3 类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
-
4 类模板对象做函数参数
——类模板实例化的对象,向函数传参的方式
有3中方式:
- 指定传入的类型——直接显示对象的数据类型(最常用)
- 参数模板化——将对象中的参数变为模板进行传递
- 整个类模板化——将这个对象类型 模板化进行传递
-
5 类模板与继承
注:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 若向灵活指定出父类中T的类型,需将子类变为类模板
-
6 类模板成员函数类外实现
-
构造函数类外实现
-
成员函数类外实现
注:类模板中成员函数类外实现时,需要加上模板参数列表
-
-
7 类模板分文件编写
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:1)直接包含.cpp源文件
2)将声明.cpp和实现.h写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
总结:主流写法是第二种,将类模板和成员函数写到一起,并将后缀名改为.hpp
-
8 类模板和友元
掌握类模板配合友元函数的类内和类外实现
- 全局函数类内实现——直接在类内声明友元即可
- 全局函数类外实现——需提前让编译器知道全局函数的存在
注:一般不用类外实现
-
9 案例——通用数组类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3M5tYVSO-1616402185533)(D:\研究生相关文件\软件学习\C++学习\图库\案例说明.JPG)]
要求:
1)可以对内置数据类型以及自定义数据类型的数据进行存储
2)将数组中的数据存储到堆区
3)构造函数中可以传入数组的容量
4)提供对应的拷贝构造函数以及operator=防止浅拷贝问题
5)提供尾插法和尾删法对数组中的数据进行增加和删除
6)可以通过下标的方式访问数组中的元素
7)可以获取数组中当前元素个数和数组的容量
3.2 STL初识
——Standard Template Library,标准模板库
目的:为了建立数据库结构和算法的一套标准,诞生了STL
1、基本概念
从广义上分为:容器(container)、算法(algorithm)、迭代器(iterator)
* 容器和算法之间通过迭代器进行无缝连接
* STL几乎所有的代码都采用了模板类或者模板函数
2、六大组件
——容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
-
容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
容器——将运用最广泛的一些数据结构实现出来
常用数据结构:数组、链表、树、栈、队列、集合、映射表等
容器分为:
1)序列式容器:强调值的排序,每个元素均有固定的位置
2)关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
-
算法:各种常用算法,如sort、find、copy、for-each等
分为:
1)质变算法:运算过程中会更改区间内元素的内容,如拷贝、替换、删除等
2)非质变算法:运算过程中不会更改区间内元素的内容,如查找、计数、遍历、寻找极值等
-
迭代器:扮演了容器与算法之间的胶合剂
算法要通过迭代器才能访问容器中的元素
作用:提供一种方法,使之能够依序寻访某个容器所含的元素个数,而又无需暴露该容器的内部表示方式
注:每个容器都有自己专属的迭代器
迭代器类似指针,初学阶段可将其理解为指针
迭代器种类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G8isSoR0-1616402185536)(D:\研究生相关文件\软件学习\C++学习\图库\迭代器种类.JPG)]
-
仿函数:行为类似函数,可作为算法的某种策略
-
适配器:一种用来修饰容器或者仿函数或迭代器接口的的东西
-
空间配置器:负责空间的配置与管理
3、STL中容器、算法、迭代器初识
-
1 vector存放内置数据类型
容器:vector
算法:for_each
迭代器:vector::iterator
-
2 vector存放自定义数据类型
-
3 vector容器嵌套容器
3.3 STL—常用容器
3.3.1 String容器
-
1、基本概念
本质:string是C++的字符串,而string本质上是一个类
string和char区别:
- char*是一个指针
- string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器
特点:string类内部封装了很多成员方法,如find、copy、delete、insert
-
2、string 构造函数
方式:
-
string(); 创建一个空的字符串
string(const char*s); 使用字符串s初始化
-
string(const string &str); 使用一个string对象初始化另一个string对象
-
string(int n,char c); 使用n个字符c初始化
-
-
3、赋值操作
赋值的函数原型:
- 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赋值给当前字符串
-
4、字符串拼接
——实现字符串末尾拼接字符串
append/+=
-
5、字符串查找和替换
——查找指定字符串是否存在
rfind和find区别:
rfind从右往左查,find从左往右查
find找到祖父串后返回查找的第一个字符串,找不到返回-1
——替换指定位置的字符串
replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串
-
6、字符串比较
比较方式:按照字符的ASCII码进行对比
=返回0;>返回1;<返回-1
compare——主要用于比较是否相等
-
7、字符存取
2种方式:
- char&operator[](int a); //通过**[]方式**取字符
- char&at(int n); //通过at方法获取字符
-
8、插入和删除
——insert和erase
-
9、子串获取
——从字符串中获取想要的子串
——substr
3.3.2 vector容器
——vector数据结构和数组非常相似,也称为单端数组
- 1、基本概念
与普通数组区别:
数组时静态空间,而vector可以动态扩展
动态扩展——并不是在原空间之后续借新空间,而是找更大的内存空间,然后将元数据拷贝新空间,释放原空间
vector一般在尾部进行删除(pop_back())/添加操作(push_back())
v.begin()——第一个元素位置
v.end()——最后一个元素的下一个位置
v.rbegin()——倒数的第一元素位置
v.rend()——第一个元素的前一个位置
insert()、front()、back()
注:vector容器的迭代器时支持随机访问的迭代器
-
2、构造函数
——创建vector容器
-
3、赋值操作
- 重载=
- assign
-
4、容量和大小
函数原型:
- empty()——判断容器是否为空
- capacity()——容器的容量
- size()——返回容器中元素的个数
- resize(int num)——重新指定容器的长度为num,若容器变长,则以默认位置填充新位置,若容器变短,则末尾超出容器长度的元素被删除
- resize(int num,elem)——重新指定容器的长度为num,若容器变长,则以elem值填充新位置,若容器变短,则末尾超出容器长度的元素被删除
-
5、插入和删除
- 尾插——push_back(ele)
- 尾删——pop_back()
- 指向位置插入——insert(位置迭代器)
- 指向删除——erase(位置迭代器)
-
删除所有元素——clear()
-
6、数据存取
- at(int idx); //返回索引idx所指的数据
- operator[]; //返回索引idx所指的数据
- front(); //返回容器中第一个数据元素
- back(); //返回容器中最后一个数据元素
-
7、互换容器
——实现两个容器元素进行互换,swap
巧用swap可收缩内存空间
vector(v).swap(v)
vector<int>(v)——匿名对象
person p3 = person(10);
左侧的如person(10)称为匿名对象 特点:当前执行结束后,系统会立即回收掉匿名对象
-
8、预留空间
——减少vector在动态扩展容量时的扩展次数
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问
3.3.3 deque容器
-
1、基本概念
功能:双端数组,可以对头端进行插入删除操作
与vector区别:
- vector对头部的插入删除效率低,数据量越大,效率越低
- deque相对而言,对头部的插入删除速度比vector快
- vector访问元素的速度比deque快,这与两者内部实现有关
-
2、构造函数
与vector相似
-
3、赋值操作
与vector相似
-
4、大小操作
与vector相似
但没有容量
-
5、插入和删除
两端插入:
- 尾插——push_back(elem)
- 头插——push_front(elem)
- 尾删——pop_back()
- 头删——pop_front()
注:插入insert和删除erase的位置时迭代器
指定位置操作:
insert、erase、clear与vector相似
-
6、数据存取
与vector相似
-
7、排序
-
8、案例
——5名选手,10个评委,去掉最高分和最低分,求平均分
3.3.4 stack容器
-
1、基本概念
——一种先进后出的数据结构,只有一个出口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMhNdIF1-1616402185539)(D:\研究生相关文件\软件学习\C++学习\图库\stack容器.JPG)]
注:栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为
empty、size
栈中进入数据——入栈(push)
栈中弹出数据——出栈(pop)
-
2、常用接口
构造函数:
- stackstk;//采用模板类实现
- 拷贝构造函数
赋值操作:重载等号
数据存取:
- push(elem);//向栈顶添加元素——入栈
- pop();//从栈顶移除第一个元素——出栈
- top(); //返回栈顶元素
大小操作:
- empty();
- size();
3.3.5 queue容器
——先进先出的数据结构,有2个出口[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gisRLhU1-1616402185540)(D:\研究生相关文件\软件学习\C++学习\图库\queue容器.JPG)]
只允许队尾进数据,队头出数据,队列不允许有遍历行为
队列中进入数据——入队push
队列中出数据——出队pop
常用接口
与stack相似
队头——front
队尾——back
大小——size
3.3.6 list容器
功能:将数据进行链式存储
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序时通过链表中的指针链接实现的
链表由一系列结点组成,节点是由数据域(存储数据元素)和指针域(存储下一个结点地址组成。
优点:动态存储分配,不会造成内存浪费和溢出
可对任意点位置进行快速插入、删除元素
缺点:容器遍历速度没有数组快
占用的空间比数组大
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-esMJ50w7-1616402185541)(D:\研究生相关文件\软件学习\C++学习\图库\list容器.JPG)]
迭代器只支持前移和后移,属于双向迭代器
-
构造函数
与vector相似
-
赋值和交换
- assign
- 重载=
- swap
与vector相似
-
大小操作
与vector相似
- size()
- resize()
-
插入和删除
函数:
- push_backelem();//尾插
- pop_back();//尾删
- push_front(elem);//头插
- pop_front();//头删
- insert(pos,elem);
- insert(pos,n,elem);
- insert(pos,beg,end);
- clear();
- erase(beg,end);//删除区间内的数据,返回下一个数据的位置
- erase(pos); //删除pos位置的数据,返回下一个数据的位置
- remove(elem);//删除容器中所有与elem值匹配的元素
-
数据存取
-
front();//返回第一个元素
-
back();//返回最后一个元素
注:不支持at和[]访问
如何判断迭代器是都支持随机访问:
list::iterator it=L.begin()
it++;it–;不报错,支持双向
it=it+1;支持随机访问
-
-
反转和排序
- reverse();//反转链表
- sort(); //链表排序
注:所有不支持随机访问迭代器的容器,不可以用标准算法
不支持随机访问迭代器的容器,内部会提供一些算法(成员函数)
-
排序案例
——将自定义数据类型进行排序,person中属性有姓名、年龄、身高
年龄升序,身高降序
3.3.7 set/multiset容器
-
基本概念
所有元素都会在插入时自动被排序
本质:属于关联式容器,底层结构式用二叉树实现
-
构造和赋值
——默认构造和拷贝构造
——赋值——重载等号
注:插入不用push,用insert
-
大小和交换
——size()、empty()、swap()
-
插入和删除
insert(elem);
clear();
erase(pos);
erase(beg,end);
erase(elem);//删除容器中值尾elem的值
-
查找和统计
函数原型:
-
find(key); //查找key是否存在,若存在,返回改键的元素的迭代器,不存在,返回set.end()
-
count(key;) //统计key的个数
-
set和multiset区别
- set不允许容器中有重复的元素,set插入数据的同时会返回插入结构,表示插入是否成功
- multset允许容器中有重复的元素
-
pair对组创建
——承兑出现的数据,利用队组可以返回2个数据
2种创建方式:
- pair(type,type) p (val1,val2);
- pair(type,type) p =make_pair(val1,val2)l
-
set容器排序
——利用仿函数,可以改变排序规则
3.3.8map/multimap容器
-
基本概念
所有元素都是pair;
pair在第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
索引元素都会根据元素的键值自动排序
本质:属于关联式容器,底层结构用二叉树实现
优点:可根据key值快速找打value值
map和multimap区别:
前者不允许有重复key值元素,后者允许有
-
构造和赋值
所有元素都是承对出现,插入数据时要使用对组
-
大小和交换
size()、empty()、swap();与set相似
-
插入和删除
insert()
clear()
erase(pos);
erase(bedg,end)
ease(key)
-
查找和统计
find(key);查找key是否存在,若存在,返回改键的元素的迭代器,若不存在,返回set.end();
count(key),统计key元素的个数
-
容器排序
利用仿函数改变排序规则
-
案例——员工分组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RXJFVx1-1616402185542)(D:\研究生相关文件\软件学习\C++学习\图库\员工分组.JPG)]
3.4 STL—函数对象
3.4.1 函数对象
-
概念
重载函数调用操作符的类,其对象称为函数对象
函数对象使用重载的()时,行为类似函数调用,也叫仿函数
本质:函数对象(仿函数)是一个类,不是一个函数
-
使用
特点:可像普通函数调用,可以有参数,可以有返回值
超出普通函数概念,可以有自己的状态
可作为参数传递
3.4.2 谓词
-
概念
返回bool类型的仿函数称为谓词;如果operator()接受一个参数,称为一元谓词,接受两个参数,则称为二元谓词
3.4.3 内建函数对象
——使用时,需要引入头文件#include
-
算术仿函数
功能描述:
- 实现四则运算
- 其中negate是一元运算,其它都是二元运算
仿函数原型
- teplate T plus
- teplate T minus
- teplate T multilies
- teplate T divides
- teplate T modules;取模仿函数
- teplate T negate;取反仿函数
-
关系仿函数
——实现关系对比
equal_to
not_equal
greater
greater_equal
less
less_equal
-
逻辑仿函数
logical_and
logical_or
logical_not
3.5常用算法
——主要由头文件、、
3.5.1 常用遍历算法
-
for_each
函数原型:for_each(iterator beg,end,_func)
_func函数或者仿函数对象(普通函数名称,或者仿函数对象)
-
transform——搬运容器到另一个容器
函数原型:transform(iterator_beg1,iterator_end1,iterator beg2,_func)
beg1——源容器开始迭代器
beg2——目标容器开始迭代器
注:目标容器需要提前开辟空间
3.5.2 常用查找算法
-
find
——查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()
函数原型:find(iterator beg,iterator end,value)
-
find_if
——按条件查找
函数原型:find_if(iterator beg,iterator end,_Pred)
_Pred函数或者谓词(返回bool类型的仿函数)
-
adjacent_find
——查找相邻重复元素,返回相邻元素的第一个位置的迭代器
函数原型:adjacent_find(iterator beg,iterator end)
-
binary_search
——查找指定元素是否存在,查到返回true,不存在返回false
函数原型:binary_search(iterator beg,iterator end,value)
注:虽然效率高,但在无序序列中不可用
-
count
——统计元素个数(出现次数)
函数原型:count(iterator beg,iterator end,value)
-
count_if
——按条件统计元素个数(出现次数)
函数原型:count_if(iterator beg,iterator end,_Pred)
3.5.3 常用排序算法
-
sort
——对容器内元素进行排序
函数原型:sort(iterator beg, iterator end, _Pred)
qsort
——使用快速排序例程排序
函数原型:void qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *));
参数:1 待排序数组首地址
2 数组中待排序元素数量
3 各元素的占用空间大小
4 指向函数的指针,用于确定排序的顺序
**qsort要求提供的函数是需要自己定义的一个比较函数,比较函数使得qsort通用性更好。**有了比较函数qsort可以实现对数组、字符串、结构体等结构进行升序或降序排序。
-
random_shuffle
——洗牌,指定范围内的元素随机调整次序
函数原型:random_shuffle(iterator beg, iterator end)
注:使用时记得加随机种子
-
merge
——合并两个容器元素,并存储到另一容器中
注:合并的2个容器必须是有序的
函数原型:merge(iterator beg1, iterator end1,iterator beg2, iterator end2,iterator dest)
iterator dest:目标容器开始迭代器
注:目标容器需提前开辟空间
-
reverse
——反转容器内元素
函数原型:reverse(iterator beg, iterator end);
3.5.4 常用拷贝和替换算法
-
copy
函数原型:merge(iterator beg, iterator end,iterator dest);
按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置
注:目标容器需提前开辟空间
-
replace
——将容器内指定范围的旧元素改为新元素
函数原型:replace(iterator beg, iterator end,oldvalue,newvalue)
-
replace_if
——将区间内满足条件的元素替换为指定元素
函数原型:replace_if (iterator beg, iterator end,_Pred,newvalue)
-
swap
——互换2个容器中的元素
函数原型:swap(container c1,container c2);
注:2个容器应为同种类型的容器
3.5.5常用算术生成算法
——使用时应包含头文件
-
accumulate
——计算容器类元素累计总和
函数原型:accumulate(iterator beg, iterator end,value)
value是起始累加值,可写为0;
-
fill
——向容器指定区间内填充指定的元素
函数原型:fill (iterator beg, iterator end,value))
value——填充值
3.5.6 常用集合算法
-
set_intersection
——求2个容器的交集
函数原型:
set_inersection(iterator beg1, iterator end1,iterator beg2, iterator end2,iterator dest);
iterator dest——目标容器开始迭代器
注:目标容器需提前开辟空间,(最特殊情况大容器包括小容器,开辟空间取小容器的size) min(container c1.size(),container c2.size())
返回值为交集最后一个元素位置
两个容器必须是有序的
-
set_union
——求2个容器的并集
函数原型:
set_union(iterator beg1, iterator end1,iterator beg2, iterator end2,iterator dest);
注:两个容器必须是有序的
目标容器需提前开辟空间,空间为两个容器之和,c1.size()+c2.size();
返回值为并集最后一个元素位置
-
set_difference
——求2个容器的差集
函数原型:
set_union(iterator beg1, iterator end1,iterator beg2, iterator end2,iterator dest);
注:两个容器必须是有序的
目标容器需提前开辟空间,空间大容器size();max(c1.size(),c2.size());
返回值为差集最后一个元素位置
补充点
1、#ifdef和#ifndef
在头文件中使用#ifdef和#ifndef是非常重要的,可以防止双重定义的错误
一般格式是这样的:
#ifndef <标识>
#define <标识>
…
…
#endif
2、auto用法
——自动推导
-
用于代替冗长复杂、变量使用范围专一的变量声明
std::vectorstd::string vs;
旧版:for (std::vectorstd::string::iterator i = vs.begin(); i != vs.end(); i++)
新版:for (auto i = vs.begin(); i != vs.end(); i++)
void func(int n) { std::cout << n << std::endl; } int main() { std::vector<int> arr; arr.push_back(1); arr.push_back(2); std::for_each(arr.begin(), arr.end(), func); return 0; } //等同于 int main() { std::vector<int> arr; arr.push_back(1); arr.push_back(2); for (auto n : arr) { std::cout << n << std::endl; } return 0; }
-
在定义模板函数时,用于声明依赖模板参数的变量类型
template <typename _Tx,typename Ty>
_void Multiply(_Tx x, _Ty y)
{
auto v = x*y;
std::cout << v;
}
-
模板函数依赖于模板参数的返回值
template <typename _Tx, typename _Ty> _
auto multiply(_Tx x, _Ty y)->decltype(_Tx*_Ty)
{
return x*y;
}
注意事项:
-
auto 变量必须在定义时初始化,这类似于const关键字。
-
定义在一个auto序列的变量必须始终推导成同一类型。例如:
auto a4 = 10, a5 = 20, a6 = 30;//正确 auto b4 = 10, b5 = 20.0, b6 = 'a';//错误,没有推导为同一类型
使用auto关键字做类型自动推导时,依次施加一下规则:
- 如果初始化表达式是引用,则去除引用语义。
int a = 10; int &b = a; auto c = b;//c的类型为int而非int&(去除引用) auto &d = b;//此时c的类型才为int& c = 100;//a =10; d = 100;//a =100;
- 如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义。
const int a1 = 10; auto b1= a1; //b1的类型为int而非const int(去除const) const auto c1 = a1;//此时c1的类型为const int b1 = 100;//合法 c1 = 100;//非法
- 如果auto关键字带上&号,则不去除const语意。
const int a2 = 10; auto &b2 = a2;//因为auto带上&,故不去除const,b2类型为const int b2 = 10; //非法
这是因为如何去掉了const,则b2为a2的非const引用,通过b2可以改变a2的值,则显然是不合理的。
- 初始化表达式为数组时,auto关键字推导类型为指针。
int a3[3] = { 1, 2, 3 }; auto b3 = a3; cout << typeid(b3).name() << endl;
程序将输出
int *
- 若表达式为数组且auto带上&,则推导类型为数组类型。
int a7[3] = { 1, 2, 3 }; auto & b7 = a7; cout << typeid(b7).name() << endl;
3、typedef
作用:给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型。不管这个声明中的标识符号出现在中间还是最后.
先从初级的开始:
typedef int x; // 定义了一个名为x的int类型
结构体
typedef struct { char c; } s; // 定义名为s的struct类型
指针
typedef int *p; //定义了一个名为p的指针类型, 它指向int (中文描述指针好累)
接下来是高级的(注意标识符不一定在最后):
数组
typedef int A[]; // 定义一个名为A的ints数组的类型
函数
typedef int f(); // 定义一个名为f, 参数为空, 返回值为int的函数类型
typedef int g(int); // 定义一个名为g, 含一个int参数, 返回值为int行的函数类型
现在回过头看:
typedef int P();
static P(Q);
应该就比较好理解了, P是一个新定义的function类型, 它返回值为int, 无参数
根据我的第2点说明, P(Q); 实际上等价于P Q, 声明Q是一个返回值为int, 无参数的函数.
4、memset函数
memset(void *s,int ch,size_t n)
说明:是计算机中C/C++语言函数。将s所指向的某一块内存中的前n个字节的内容全部设置为ch指定的 ASCII值, 第一个值为指定的内存地址,块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工 作, 其返回值为指向s的指针。所在头文件<memory.h>或<string.h>。
将已开辟内存空间 s 的首 n 个字节的值设为值 c。
1、常用于内存空间初始化,如:
char str[100];
memset(str,0,100);
2、来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘ ’或‘/0’;
har a[100];
memset(a, '/0', sizeof(a));
5、printf用法
——需要调用<stdio.h>
函数原型:
int printf(const char *format[,argument]);
format:由格式说明和普通字符构成。格式说明定义argument的显示格式,以“%”开头,后接格式字符。格式字符前还可以有其他修饰符,表述输出的宽度、精度等。
format样式如下:
%[flags][width][.precision][length]格式字符
格式字符:
格式字符 | 说明 | 示例 |
---|---|---|
d或i | 带符号十进制整数 | 521,2019 |
o | 无符号八进制数 | 316,777 |
x | 无符号十六进制数 | 8cf,32a7 |
u | 无符号十进制整数 | 123,1 |
f | 小数形式单、双精度浮点数 | 102.6,13.14 |
e或E | 指数形式的单、双精度浮点数 | 1.026e+2 |
g或G | 以%f或%e形式输出浮点数 | 102.6 |
c | 单个字符 | ‘y’,‘z’ |
s | 字符串 | “Love” |
p | 输出一个指针 | 0012FF7C |
flags:对齐方式
flags | 说明 |
---|---|
- | 在给定的域内靠左输出 |
+ | 强制在正数前输出+,负数前输出- |
# | 使用o、x、X时,分别在数据前加前导符0、0x、0X输出 |
width:宽度
width | 说明 |
---|---|
m | 输出字段宽度,若数据宽度小于m,则左端补上空格;否则按实际位数输出 |
precision:精度
precision | 说明 |
---|---|
a | 对于浮点数,表示输出a位小数;对于字符串,表示输出字符串个数 |
6、scanf用法
函数原型:
int scanf(const char *format[,argument]);
【函数参数】
format:由格式说明、空白字符与非空白字符构成。格式说明指示了argument的输入格式,以“%”开头,后接格式字符。格式字符前还可以有其他修饰符。
format样式如下:
%[*][width][modifiers]格式字符
argument表示参数的地址!要加“&”!
格式字符:
格式字符 | 说明 |
---|---|
d或i | 带符号十进制整数 |
o | 无符号八进制数 |
x或X | 无符号十六进制数 |
u | 无符号十进制整数 |
f、e、E、g、G | 浮点数 |
c | 单个字符 |
s | 字符串 |
修饰符说明:
修饰符 | 说明 |
---|---|
* | 跳过读入数据,不存入对应的argument中 |
width | 指定输入数据所占用的宽度 |
modifiers | 指定由d、i、x、X、o、u、e、f、g说明的字符的大小 |
7、rand和srand用法
#include<cstdlib>
#include<ctime>
rand();//产生随机数,返回一个从0到最大随机数的任意整数
rand()%100;//产生0-99的数
rand()%101+100;//产生100-200
srand(int(time(0)));//随机种子
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())//判断是否打开成功
{
cout << "文件打开失败" << endl;
return;
}
总结来说,可以表示为:int num = rand() % n +a;
其中的a是起始值,n-1+a是终止值,n是整数的范围。
一般性:rand() % (b-a+1)+ a ; 就表示 a~b 之间的一个随机整数。
若要产生01之间的小数,则可以先取得010的整数,然后均除以10即可得到“随机到十分位”的10个随机小数。
RAND_MAX 的范围最少是在 32767 之间(int),即双字节(16位数)。
若用unsigned int 双字节是 65535,四字节是 4294967295 的整数范
|
| d或i | 带符号十进制整数 | 521,2019 |
| o | 无符号八进制数 | 316,777 |
| x | 无符号十六进制数 | 8cf,32a7 |
| u | 无符号十进制整数 | 123,1 |
| f | 小数形式单、双精度浮点数 | 102.6,13.14 |
| e或E | 指数形式的单、双精度浮点数 | 1.026e+2 |
| g或G | 以%f或%e形式输出浮点数 | 102.6 |
| c | 单个字符 | ‘y’,‘z’ |
| s | 字符串 | “Love” |
| p | 输出一个指针 | 0012FF7C |
flags:对齐方式
flags | 说明 |
---|---|
- | 在给定的域内靠左输出 |
+ | 强制在正数前输出+,负数前输出- |
# | 使用o、x、X时,分别在数据前加前导符0、0x、0X输出 |
width:宽度
width | 说明 |
---|---|
m | 输出字段宽度,若数据宽度小于m,则左端补上空格;否则按实际位数输出 |
precision:精度
precision | 说明 |
---|---|
a | 对于浮点数,表示输出a位小数;对于字符串,表示输出字符串个数 |
6、scanf用法
函数原型:
int scanf(const char *format[,argument]);
【函数参数】
format:由格式说明、空白字符与非空白字符构成。格式说明指示了argument的输入格式,以“%”开头,后接格式字符。格式字符前还可以有其他修饰符。
format样式如下:
%[*][width][modifiers]格式字符
argument表示参数的地址!要加“&”!
格式字符:
格式字符 | 说明 |
---|---|
d或i | 带符号十进制整数 |
o | 无符号八进制数 |
x或X | 无符号十六进制数 |
u | 无符号十进制整数 |
f、e、E、g、G | 浮点数 |
c | 单个字符 |
s | 字符串 |
修饰符说明:
修饰符 | 说明 |
---|---|
* | 跳过读入数据,不存入对应的argument中 |
width | 指定输入数据所占用的宽度 |
modifiers | 指定由d、i、x、X、o、u、e、f、g说明的字符的大小 |
7、rand和srand用法
#include<cstdlib>
#include<ctime>
rand();//产生随机数,返回一个从0到最大随机数的任意整数
rand()%100;//产生0-99的数
rand()%101+100;//产生100-200
srand(int(time(0)));//随机种子
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())//判断是否打开成功
{
cout << "文件打开失败" << endl;
return;
}
总结来说,可以表示为:int num = rand() % n +a;
其中的a是起始值,n-1+a是终止值,n是整数的范围。
一般性:rand() % (b-a+1)+ a ; 就表示 a~b 之间的一个随机整数。
若要产生01之间的小数,则可以先取得010的整数,然后均除以10即可得到“随机到十分位”的10个随机小数。
RAND_MAX 的范围最少是在 32767 之间(int),即双字节(16位数)。
若用unsigned int 双字节是 65535,四字节是 4294967295 的整数范