C++学习笔记

注*:以下笔记内容根据黑马程序员教程所做,传至线上仅为方便实时查阅,因个人疏忽、学艺不精或可导致笔记内容有误,望请阅览者不吝指出*

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
++自增运算符,整数值增加 1A++ 将得到 11
自减运算符,整数值减少 1A-- 将得到 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会先执行一次循环语句,再判断循环条件

  1. 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 的整数范

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值