• C++万字自学笔记


    [@TOC]

    一、 C++基础

    C++的IDE有CLion、Visual Studio、DEV C++、eclipse等等,这里使用CLion进行学习。

    0. C++初识

    0.1 第一个C++程序

    编写一个C++程序总共分为4个步骤

    • 创建项目
    • 创建文件
    • 编写代码
    • 运行程序
    #include 
    
    int main()
    {
    	using namespace std;
    	cout << "Come up and C++ me some time.";
    	cout << endl;
    	cout << "You won't regret it!" << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • C语言中省略int只写main()相当于函数的类型为int,相当于int main(),C++中不建议省略
    • C++中int main()等价于int main(void)
    • C++中main函数可以省略return 0;

    0.2 注释

    作用:在代码中加一些说明和解释,方便阅读代码

    两种格式

    1. 单行注释//注释
      • 通常放在一行代码的上方或者一条语句的末尾,对该行代码进行说明
    2. 多行注释/*多行注释*/
      • 通常放在一行代码上方,对该行代码进行整体说明

    编译器在编译代码时会忽略注释的内容

    0.3 变量

    作用:给一段指定的内存空间起名,方便操作这段内存

    语法数据类型 变量名 = 初始值;

    变量声明的语法

    typename variablename;
    
    int year;
    
    • 1
    • 2
    • 3

    初始化

    //C++98中可以使用初始化数组的方式初始化变量
    int num = {10};
    //C++11中这种用法更常见,也可以不使用=
    int num{10};
    //{ }也可以不包含东西
    int num{};//此时变量的值初始化为0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在C++11中可以将 { }大括号初始化器用于任何类型({}前面可以使用=,也可以不使用=),这是一种通用的初始化语法


    0.3常量

    作用:用于记录程序中不可更改的数据

    C++定义常量的两种方式

    1. #define 宏常量:#define 常量名 常量值
      • 通常在文件上方定义,表示一个常量
    2. const修饰的变量const 数据类型 常量名 = 常量值
      • 通常在变量定义前加关键字const,修饰该变量为常量,不可修改

    exp:

    //1、宏常量
    #define day = 7;
    
    int main()
    {
    	cout<<"一周总共有"<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    0.4 关键字

    作用:关键字时C++中预先保留的标识符

    C++关键字如下:

    asmdoifreturntypedef
    autodoubleinlineshorttypeid
    booldynamic_castintsignedtypename
    breakelselongsizeofunion
    caseenummutablestaticunsigned
    catchexplicitnamespacestatic_castusing
    charexportnewstructvirtual
    classexternoperatorswitchvoid
    constfalseprivatetemplatevolatile
    const_castfloatprotectedthiswchar_t
    continueforpublicthrowwhile
    defaultfriendregistertrue
    deletegotoreinterpret_casttry

    提示:在给变量或者常量起名称时候,不要用C++得关键字,否则会产生歧义。

    0.5 标识符命名规则

    作用:C++规定给变量和常量命名有一定的规则

    • 标识符不能是关键字
    • 标识符只能由字母、数字、下划线组成
    • 第一个字符必须为字母或下划线
    • 标识符中字母区分大小写

    建议:给标识符命名时,争取做到见名知意的效果,方便自己和他人的阅读

    1. 数据类型和运算符

    1.1 数据类型

    创建一个变量或者常量时必须指出相应的数据类型,否则无法给变量分配内存空间

    数据类型存在的意义是方便编译器分配空间

    1.1.1 整型
    变量类型所占字节大小
    short至少16位 64位windows下sizeof short = 2字节=16位,同下
    int至少与short一样长 sizeof int = 4,32位
    long至少32位且至少与int一样长 sizeof long = 4,32位
    long long至少64位且至少与long一样长 sizeof long long = 8,64位

    无符号类型:

    1. 在变量类型前加unsigned可以使之称为无符号类型的变量,如果short类型变量所占字节大小为2,所占位数16位,则第一位用于表示变量的正负,则可用位数为15位,能表示的范围为-215~215,如果添加了unsigned关键字,则无符号位,可以表示的数据大小为0~2^16,无负数。

    2. unsigned是unsigned int的缩写

    3. 无符号类型的优点是可以增大变量能够存储的范围(仅当数值不为负时)

    一个整型数据在不设置数据类型的情况下默认为int型,如果不想使用int可以通过设置后缀可以为一个值确定数据类型

    unsigned可以用u表示
    long可以用L表示
    unsigned和long的后缀可以叠加使用:
    ul表示unsigned long
    ull表示unsigned long long
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不区分大小写

    查看数据类型所占空间大小使用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    C++中进制的表示

    十进制
    八进制以0开头
    十六进制以0x开头

    不管数据以几进制的形式表现出来,在内存中都是以二进制进行存储

    C++中以四种进制进行输出

    #include 
    #include 
     
    using namespace std;
     
    int main()
    {
        int a=64;
        cout<<(bitset<32>)a<<endl;//二进制32位输出
        cout<<oct<<a<<endl;
        cout<<dec<<a<<endl;
        cout<<hex<<a<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1.1.2浮点型(实型)

    浮点数能表示带小数的数字

    浮点数有两种表示方法:

    1. 标准小数点表示法

      exp:

      3.14
      12.3
      
      • 1
      • 2
    2. 科学计数法

      exp:

      3.45e6
      10.12E-6
      5.98E+10
      
      • 1
      • 2
      • 3

    同科学计数E、e(不区分大小写),表示10的多少次方

    int main()
    {
    	float f1 = 3.14f;
        double d1 = 3.14;
        
        cout<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3种浮点类型:

    float至少32位64位windows中:sizeof float = 4字节32位,7位有效数字
    double至少48位sizeof double = 8,64位,15~16位有效数字
    long doublesizeof long double = 16,128位

    默认情况下浮点数是double类型的,如果想使用float类型,可以在值后面加上后缀F或者f

    使用long double类型可以在值后面加上L或者l,因为l与1不好分辨,建议使用L

    1.1.3 字符型

    作用:字符型变量用于显示单个字符

    语法char ch = 'a';

    1. 使用单引号
    2. 单引号内只能有一个字符

    char类型常用于表示字符与小整数,大小为1个字节,表示范围-128~127

    char类型也可以使用unsigned关键字修饰,unsigned char表示的范围是0~255

    char型使用单引号 ’ ’ 括起来

    exp:

    char ch = 'a';
    
    • 1

    字符型变量并不是把字符本身放到内存中,而是将对应的ASCII编码放入存储单元

    ASCII码表格:

    ASCII控制字符ASCII字符ASCII字符ASCII字符
    0NUT32(space)64@96
    1SOH33!65A97a
    2STX34"66B98b
    3ETX35#67C99c
    4EOT36$68D100d
    5ENQ37%69E101e
    6ACK38&70F102f
    7BEL39,71G103g
    8BS40(72H104h
    9HT41)73I105i
    10LF42*74J106j
    11VT43+75K107k
    12FF44,76L108l
    13CR45-77M109m
    14SO46.78N110n
    15SI47/79O111o
    16DLE48080P112p
    17DCI49181Q113q
    18DC250282R114r
    19DC351383S115s
    20DC452484T116t
    21NAK53585U117u
    22SYN54686V118v
    23TB55787W119w
    24CAN56888X120x
    25EM57989Y121y
    26SUB58:90Z122z
    27ESC59;91[123{
    28FS60<92/124|
    29GS61=93]125}
    30RS62>94^126`
    31US63?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范围09,af,A~F3位16进制

    示例:

    int main()
    {
    	cout<<"\\"<
1.1.4 布尔型 bool

作用:布尔数据类型代表真或假的值

语法bool x;

布尔型有两个值:

占用一个字节

exp:

int main()
{
    bool flag = true;
    cout<

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进行整型提升

强制类型转换

语法:

exp:

(int)1.5;//C语言中的方式
int(1.5);//C++中的方式

在C++中使用typeid(typename).name()查看数据类型名称

exp:

cout<

C++中的缩窄转换

C++中的列表初始化(使用{}进行初始化)不允许缩窄转换(narrawing convertions),即变量的类型无法表示赋给它的值。

C++中的auto声明

auto是C++11中根据初始值类型推断变量的类型的关键字

作用:在初始化声明中如果使用auto,而不指定变量的类型,编译器会把变量的类型设置成与初始值相同

常用于STL中

exp:

vector scores;
vector::iterator v = scores.begin();
//可以简写为
auto v = scores.begin();

算术运算符

加法:+


减法:-


乘法:*


除法:/

除法分为:

即使使用float接收参数结果也是取整的,因为运算符进行运算时就已经根据运算规则进行了取整

C:

float a = 9/2;
cout<

根据运算规则,操作数会自动类型转换,int转float再运算


求模:%


算术运算符的优先级

递增递减运算符

++自增
自减

组合赋值运算符

+=
-=
*=
/=
%=

关系运算符

关系运算符不能用于C风格的字符串,可以用于string类型的字符串,对于关系表达式,如果判断结果为真,则返回true,如果为假返回false,对于C风格的字符串,如果使用 == 运算符进行判断则判断两者的地址是否相等。如果要判断内容是否相等,可以使用strcmp()函数,该函数接收两个字符串地址作为参数。

<小于
<=小于等于
>大于
>=大于等于
==等于
!=不等于

2. 数组

数组的声明和初始化

数组的声明typename arrayname[arraySize];

exp:

int array[10];

数组的初始化:

  1. 使用逗号分隔的值列表(初始化列表):{}
int array[3] = {1,2,3};
  1. 使用数组下标为数组元素赋值
int array[3];
array[0] = 0;
array[1] = 1;
array[2] = 2;

如果初始化数组时 [ ] 中为空则编译器自动计算元素个数

int array[] = {0};

注意事项:

C++11数组初始化

C++11中新增列表初始化( { } )的方式

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. 两个中括号必须有一个有值,否则编译器无法确定二维数组的排列方式

  2. 二维数组在内存空间中的排列方式与一维数组一致,都是连续的内存空间,编译器将其分为几个一维数组并且每个一维数组中的元素又包含了一个一维数组

  3. 在一维数组中数组名是指向数组的起始地址的指针,也是第1个元素的地址,在二维数组中同理,数组名arr=&arr[0],因为arr[0]也是一个一维数组,所以数组名也等于&arr[0][0],但本质上还是arr[0][0]的地址。因为对arr解引用两次才能取到值

  4. 使用指针的方式访问数组中的元素:

    *(*(arr+1)+1)取的是arr[1][1]的值
    
    arr:arr是一个指针,指针存放的是arr[0]的地址
    
    arr+1:指针+1表示指针指向的地址向后移动1 * sizeof(typename)位
    
    *(arr+1):指针解引用出来是arr[1]这个地址,同时arr[1]也是一个一维数组,arr[1]相当于数组名,也是个指针,指向arr[1]这个数组的第一个元素arr[1][0]
    
    *(arr+1)+1:arr[1]这个数组所指向的地址arr[1][0]向后移动1 * sizeof(typename)位指向arr[1][1]这个地址
    
    *(*(arr+1)+1):将指向arr[1][1]这个地址的指针解引用得到arr[1][1]这个地址中的值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

字符串

字符串是一个或多个字符的序列,是用双引号括起来的。

exp:

"Today is Thursday!"

C语言中没有指定存储字符串的基本数据类型,C语言中的字符串都是由char类型的数组组成的,字符串的末尾一定是以’\0’结束的,因此数组的容量必须比字符的数量多1

字符串有两种声明方式:

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():

3. 结构体

语法:

struct stname{
	typename1 variable1;
	typename2 variable2;
};

stname s {value1,value2};//C++中的初始化列表

cout<

C++中初始化结构体时可以省略struct关键字

而c语言中需要加struct关键字

常用typedef关键字与结构体一起使用

typedef struct student{
    string name;
    int age;
}stu;//给student这个结构体起一个别名

结构体的内存对齐

结构体数组

声明:

stname s[] = {
    {value1,value2},
    {v1,v2}
};
结构体指针&结构体数组&结构体指针数组
#include 

using 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_value=3.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;
    cout<<"a = "<

a = 10
p = 10
修改后
p = 20

可以修改指针指向的值,不可以修改地址

const在 p 前就是修饰p,p是地址,所以地址不可以改

常量指针:

常量指针的含义是指向常量的指针

    int a = 10,b = 20;
    const int *p = &a;

    cout<<"*p = "<<*p<

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操作符

#include 
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<<*p<<endl;//内存已经被释放了

	system("pause");
}

使用delete只能删除使用new产生的内存

在堆区new一个数组

#include 
using namespace std;

int* func()
{
    int* arr = new int[10];//使用new关键字返回的是指针
    return arr;
}
int main()
{
    int *arr = func();
    cout<<arr[0]<<endl;
    for(int i=0;i<10;i++)//给数组赋值
    {
        arr[i] = i+1;
    }
    for(int i=0;i<10;i++)//打印数组
    {
        cout<<arr[i]<<endl;
    }
    //释放数组
    delete[] arr;
    arr = NULL;
}

释放数组对象时使用delete[]

在C++中数组名表示的是地址

指针指向的数组的访问

直接使用指针名[number]即可访问,不需要*指针名
指针名+1表示指针指向数组下一个元素,解引用*(p+1)表示下一个值

在数组中数组名和指针的区别:

数组名是常量而指针是变量

7. 循环和关系表达式

for循环

for(int i=0;i<5;i++)
{
	cout<<"第"<

程序执行的顺序:

  1. 设置初始值,初始化循环变量 i
  2. 执行测试,判断 i 是否符合要求
  3. 执行循环体内容,打印输出
  4. 更新用于测试的值, i++
#include 
#include 
using namespace std;

int main()
{
    char ch[] = "abcd";
    for(int i=0, j=strlen(ch)-1; i
基于范围的for循环(C++11)(ranged-base)

C++11中引入的新特性,可以遍历容器或者其他序列的所有元素

语法:for(声明:表达式){语句}

声明建议使用auto进行自动类型推断,如果要修改值可以使用引用的方式

double arr = {12.1,3.14,5.23,3.56};
for(double x:arr)
{
	cout<
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;
}
else
	if(条件表达式2)
	{
		语句2;
	}
	else
		if(条件表达式3)
		{
			语句3;
		}
		else
		{
			语句4;
		}

其实是多个if else的嵌套,一般写为下面这种形式

if(条件表达式)
{
	语句1;
}
else if(条件表达式2)
{
	语句2;
}
else if(条件表达式3)
{
	语句3;
}
else
{
	语句4;
}
逻辑表达式

逻辑或 ||,||两边有一个或者都为真时返回真,可以使用or替代

用法:

5>3||5<4

因为||为顺序点,所以运算符左边的表达式优先于右边的表达式

i++ < 6 || i == j;

假设 i 的值为10,则在 i 与 j 进行比较时,i 的值为11

||左边表达式为真时右边的表达式不会执行

#include 

using namespace std;

int main()
{
    1||cout<<"||后边不执行"<

**逻辑与&&**可以使用and替代

两侧的表达式都为真则返回真

第一个为假则直接返回假,不再判断右侧表达式

用于确定取值范围:

if(age>17&&age<35)
{
	...
}

逻辑非也可以使用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. 文件读写

写文件

使用文件读写的主要步骤:

  1. 包含头文件fstream
  2. 创建一个ofstream对象
  3. 将这个ofstream同一个文件关联起来
  4. 像使用cout那样使用ofstream
#include 
#include 
using namespace std;

int main()
{
	ofstream of;//创建一个输出流对象
    of.open("test.txt");//与文件关联起来
    if(of.is_open())
    {
        of<<"这句话会输出到文件中";
        of.close;//关闭文件
    }
}

​ 创建的文件对象和cout用法一样,对象的方法也一样,setf(), precision()

  1. 运行程序之前,要绑定的文件不存在则自动创建这个文件,如果存在,则覆盖写入
  2. 在写文件之前还要判断文件是否打开

读文件

方法与写文件类似使用 istream 创建对象

#include 
#include 
using namespace std;

int main()
{
    string s;
    ifstream ifs;
    ifs.open("test.txt");
    if(!ifs.is_open())
    {
        exit(-1);//如果文件打开失败,退出程序
    }
    else
    {
        ifs>>s;//读取文件内容给字符串s
        cout<

exit(-1)用于退出程序

I/O操作中的方法:

方法作用
open(文件名,)将文件对象绑定文件
is_open()判断文件是否打开
eof()判断是否读取到文件尾,是返回true
fail()如果最后一次文件读取发生了类型不匹配和读取到文件尾,则返回true
bad()如果文件受损或硬件问题,则返回true
good()该方法在没有任何问题的情况下返回true

10. 函数

函数的作用是将一段经常使用的代码封装起来,减少重复代码,一个较大的程序一般分为若干块,每个模块实现特定的功能

函数的定义

函数的定义一般主要有5个步骤:

1、返回值类型

2、函数名

3、参数表列

4、函数体语句

5、return 表达式

语法:

返回值类型 函数名 (参数列表)
{

       函数体语句

       return表达式

}

函数原型语句(函数的声明):

作用

  1. 在C++使用函数时,C++编译器需要知道这个函数的参数类型和返回值类型,C++中使用函数原形来提供这些信息。
  2. 当函数写在main函数之后时,编译器从上往下执行,在main函数中又调用了写的函数,编译器又找不到函数的实现,这时需要在main函数之前写一个函数原型,告诉编译器有这个函数

例如:

double sqrt(double);

函数的声明可以多次,但是定义只能有一次

默认参数

作用:在调用函数时可以不传参,使用默认的参数。

语法:int func(const char *, int n = 1);

如果有多个参数需要默认参数,默认参数必须从右往左进行赋值,实参按照从左到右的方式传参,但是不能少传参

func(1, ,2);这是不允许的

占位参数

语法:

#include 

using namespace std;

void func(int a;int )
{
    cout<<"占位参数"<

函数的调用

函数和数组
#include 
#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的地址是0xc1dabff5f0大小为32
形参arr的地址是0xc1dabff5f0大小为8
the first sum = 15

  1. 数组名就是一个地址,作为参数传入函数中后编译器无法推断数组的大小,所以需要传入数组元素的个数,我们也可以指定元素的个数来进行求和等操作
  2. 数组名作为地址传入函数中,函数操作的是数组本身而不是数组的副本(比如int类型作为形参传入就是一个副本),这样可以不用再复制一个副本来占用内存空间
  3. 函数原型的作用是告知编译器这个函数的返回类型以及参数类型以及提前告知编译器存在这个函数,只不过在main函数之后,让编译器不要报错。参数类型可以只写类型不写变量名,函数定义在main函数之后需要使用函数原型,如果函数定义在main函数之前则不需要函数原型

使用const保护数组

因为数组作为参数传入数组不是以副本的形式传入的,而是传入地址,而使用地址能够修改地址中的值,所以如果在函数中使用数组,又不想误操作修改数组的话可以使用const关键字

void show_array(const double arr[], int n)
{
	for(int i=0;i

注意:使用const修饰之后数组在函数范围内是只读的,并不会修改数组本身的可读可写性

除了使用数组地址+元素个数的方式进行传参,还可以使用首地址+尾地址数组区间的方式进行传参,即传入首地址+尾地址就能够确定数组的大小,这也是STL中使用的方法

exp:

#include 
#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<<"首地址"<

结果

首地址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 

using 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))
{
    cout<
函数数组
#include 

using namespace std;
//函数指针数组(指针数组)
double fa(int n);
double fb(int n);

int main()
{
    double (*p[2])(int) = {fa, fb};
    cout<

C++中cout无法直接通过函数名输出函数地址,可以通过强制类型转换输出函数地址

int func(int);
cout<<(int*)func<

声明函数指针只需要将函数声明换为(*p)即可声明一个函数指针

double func(int);
double (*p)(int);
递归

递归程序的基本结构

void recurs(arguments)
{
    statement1;
    if(test)
    {
        recurs(arguments)
    }
    statement2;
}

内联函数

语法:

在函数声明和定义之前加关键字inline

内联函数的介绍

一般的函数在被调用的时候会跳转到存放函数的地址,调用结束后再返回。内联函数直接将再调用函数的位置创建一个该函数的副本,不需要进行跳转,程序运行速度更快,如果函数被调用十次就会被创建十个副本,需要消耗更多的内存。是典型的以空间换时间。
语法
在函数声明和定义之前加关键字inline

#include 

using namespace std;

inline double my_inline_square(double x)
{
	return x*x;
}
int main()
{
	cout<<my_inline_square(5)<<endl;
}

如果函数代码执行的时间比函数跳转的时间短,则可以节省跳转所消耗的大部分时间。
反之则无法节省时间还会浪费内存空间

在C语言中一般通过宏定义来实现这样的功能,在C++中可以使用内联函数来替代

#include "stdio.h"
#define SQUARE(x) (x)*(x)
int main()
{
	printf("%d",SQUARE(5));
	reutrn 0;
}

函数重载

重载也叫多态,是面向对象程序设计三大特点之一,意思是一个函数可以有多种形态,就像呵呵有两层意思一样。

编译器只根据函数的参数列表进行区分函数的多种形态,因此函数的返回值可以不同。

在没有重载的时候当我们传入参数与形参类型不同的时,系统可能会自动类型转换,而重载后,如果输入参数类型与形参列表类型不符则不会自动类型转换,并且报错。

#include 

using 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)
{
    cout<<n<<endl;
}

int print(double n)
{
    cout<<n<<endl;
    return 1;
}
int print(string n)
{
    cout<<n<<endl;
    return 1;
}


重载的每个函数都需要声明

函数模板

C++面向对象的一个思想是泛型编程,减少代码量,增加复用性。

语法:template

#include 
using namespace std;
template //或者使用template //T的名称可以随便起,早期的C++只有class,后期才有的typename  
void mySwap(T &a,T &b);

int main()
{
    int a = 10;
    int b = 20;
    mySwap(a,b);
    cout<<"a ="<

模板函数声明的时候也需要带上模板关键字

函数模板的作用:一个函数需要重复使用,且重复使用时参数类型不一样时可以使用函数模板,减少重复代码。

函数模板并没有创建一个函数,而是一个用于生成函数定义的方案

在使用的时候实例化为一个函数mySwap(1,2),这种方式称为隐式实例化

显式实例化,语法:template void mySwap(int, int);

模板的使用方式有两种:

自动类型转换即上述代码使用的方式mySwap(a,b)编译器会自动类型推导

手动类型选择语法:mySwap<类型名>(参数列表),即自己选择参数类型

显式具体化

显式具体化可以对结构进行操作

语法:

template<> void swap(int&,int&);

示例:

struct job{
	string name;
    int salary;
};
template 
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);//使用显式具体化
    swap(a.salary,b.salary);//显示实例化
    swap(a.salary,b.salary);//使用隐式转换
}

decltype关键字

引用

#include 

using namespce std;
int main()
{
    int a = 10;
    int &b = a;
    b = 20;
    cout<<a<<endl;
}

注意事项:

引用一般用于函数参数,不需要创建副本即可使用和修改原始数据。
在c语言中讲解形参的一个典型例子是交换两个值的函数,现在使用引用的方式展示一下

#include 

using 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;
}

c++11新特性:使用&&进行右值引用
double && res = std::sqrt(36);

引用也常用于结构体和类

  1. 在使用引用作为函数参数且不修改值的情况下建议搭配const关键字进行修饰,防止误改数据。

  2. 引用也可作为返回值使用,链式编程

例:

#include 

using 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.age<<endl;
}
person& addAge(person&p1,person&p2)
{
    p1.age = p1.age+p2.age;
    return p1;
}

命名空间

C++的新特性:命名空间

命名空间有三种格式:

可以直接使用std命名空间中的cout,cin等等

只能使用cout

使用时使用std::cout


二、 C++核心

1. 分文件编写程序

C\C++是编译型语言,运行程序之前需要编译代码,将程序源码编译为机器语言再通过链接器将.o文件链接起来,生成可执行文件。编译型语言比较依靠编译器。

单独编译
当程序比较大时,需要分模块、分文件编写程序代码,这样做的好处是每个文件都可以独立编译。当修改一个文件时,只需要重新编译修改过的文件,编译器包含编译器和链接器,编译完成后通过链接器将相关文件链接起来。

怎么分模块写程序
通常将

这种没有实例化没有生成变量只是告诉编译器如何生成一个变量或者结构的代码放在.h文件中(const或#define除外),
一个程序被分为三部分:

#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关键字将这个变量定义为内部性的变量即可正常使用。

单定义规则的一个例外是局部变量,当在函数内部或者类内部(统称为代码块内部)定义一个与全局变量重名的函数时,局部变量会隐藏(hide)掉全局变量,此时如果想要访问重名的全局变量,我们可以使用域作用解析运算符(::)来访问全局变量

总结:

全局变量

静态全局变量

全局常量

静态全局常量

局部变量

静态局部变量

局部常量

2. 内存分区模型

程序执行前:

代码区的特点:

全局区的特点:

#include 
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的地址为:"<<&a<

运行结果:

局部变量的a的地址为:0x6d1cfffafc
全局变量的g_a的地址为:0x7ff6346b4010
静态变量的地址0x7ff6346b4014
字符串常量的地址0x7ff6346b5056
全局常量的地址0x7ff6346b5004

示例2:

#include 
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<<"全局变量的地址"<

全局变量的地址
0x7ff7ec244010
0x7ff7ec244014
0x7ff7ec245004
0x7ff7ec245008
字符串常量的地址
0x7ff7ec245030
静态局部变量的地址
0x7ff7ec24500c
0x7ff7ec244018
局部变量的地址
0x8855ff64c
0x8855ff648

总结

程序执行后

栈区

注意事项:不要返回局部变量的地址

#include 
using namespace std;

int* func()
{
	int a = 10;
	return &a;
}
int main()
{
    int* p = func();
	cout<< *p <
堆区
#include 
using namespace std;

int* func()
{
	//利用new关键字开辟堆区
	int* p = new int(10);
	return p;
}
int main()
{
    int* p = func();

	cout<< *p<

动态内存

动态内存是由new关键字分配内存和delete释放内存

注意new和delete关键字是一对运算符

3. 类和对象

类的三大特性:

通用的写法:将类名大写

类的语法

#include 
#include 
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");
    cout<

封装

封装的意义:

语法:

class People
{
private://权限
	int p_name;//行为和方法
    int func(){};
};

示例:设计一个圆类

#include 
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;
    cout<<"The round of circle is:"<
类属性和行为的三种访问权限
名称权限
public类内类外都可以访问
private类内可以访问,类外不可以访问
protected类内可以访问,类外不可以访问

protected和private的区别主要在继承中体现

点和圆的关系示例:

#include 

using 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())
    {
        cout<<"on the circle"<

构造函数和析构函数

构造函数语法

特点:

构造器按参数分:

按类型分:

析构函数:

对象在销毁前会自动调用析构函数,且只会调用一次

#include 

using namespace std;
class Person
{
private:
    int age;
public:
    Person()
    {
        cout<<"这是一个无参构造(默认构造)"<
深拷贝与浅拷贝

析构函数为在堆区开辟的数据做释放

#include 

using 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<<"这是一个析构函数"<

值传递一个类会调用拷贝构造拷贝一个新的对象

值返回会返回一个拷贝对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rBcb2maZ-1688987036670)(image-20220923155843763.png)]

初始化列表

//初始化列表
Person(int a,int b,int c):m_a(a),m_b(b),m_c(c);

静态成员

静态成员变量:

访问方式:因为所有对象共享一份数据,所以除了使用对象访问,还可以使用类名进行访问

Person p;
cout<
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指针是隐含在每个非静态成员函数内的一种指针,不需要定义

作用:

示例:

#include 

using namespace std;

class Person
{
public:	
	int apple;
	Person(int apple)
	{
		this->apple = apple;
	}

	Person& addApple(Person &p)//如果使用Person作为返回值返回的是一个拷贝,使用Person&返回的是p本体
	{
		this->apple+=p.apple;
        return *this;//this指向p,解引用就是p这个对象
	}
};

void test()
{
	Person p1(1);
	Person p2(2);
	p1.addApple(p2).addApple(p2);
	cout<

空指针访问成员函数

空指针可以访问成员,但是如果访问静态成员变量会导致程序无法运行,可以在需要静态成员变量的函数添加一条判断语句,保证代码的健壮性

#include 

using namespace std;

class Person()
{
	int m_Age;
	void showClassName()
	{
		cout<<"class name is Person";
	}
	void showPersonName()
	{
		if(this==NULL)
		{
			return;
		}
		cout<<"age = "<< m_Age<showClassName();
	p->showPersonName();
}
void main()
{
	test01();
}

const修饰成员函数

常函数

常对象

#include 

using 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;//可以修改
}

友元

三种实现:

#include 

using namespace std;

class Building
{
	friend void goodGay(Building &building);
public:
	string sittingRoom;
private:
	string bedRoom;
public:
	Building()
	{
		sittingRoom="客厅";
		bedRoom="卧室";
	}
};

void goodGay(Building &building)
{
    cout<<"好机油正在访问"<

运算符重载

*可以用于定义一个指针,也可以用作解析符,也可以作为乘法运算,一个符号在不同的地方有不同的作用就是运算符的重载

在一个类中进行运算符的重载需要使用运算符函数:operator op(参数列表)

继承

继承优点:减少代码重复

语法:class 子类 : 继承方式 父类

子类也称为派生类

父类称为基类

#include 

using namespace std;

class BasePAge
{
public:
    void content(){}

    void foot()
    {
        cout<<"这是一个底部栏"<
继承方式

image-20221015202433989

继承中的对象模型

父类中所有非静态成员属性都会被子类继承下去(父类中的私有成员属性都会被被编译器隐藏,所以访问不到)

#include 

using 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;
	cout<<"sizeof Son = "<

输出结果sizeof Son = 12

继承中构造和析构顺序

子类继承父类后,当创建子类对象时,也会调用父类的构造函数

具体的执行顺序是

父类构造
子类构造
子类析构
父类析构
同名成员处理
同名函数处理

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中的所有同名成员函数(即使父类中的同名成员函数重载了也无法调用,必须加作用域)

#include 
using namespace std;

class Base
{
public:
    Base()
    {
        m_A = 100;
    }
    int m_A;

    void sayHi()
    {
        cout<<"Base: Hello World"<
同名静态成员处理

与非静态成员使用方式一致,但是静态成员的访问方式有两种

#include 

using 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 

using namespace std;

class Base1
{
public:
    int m_A=100;
};
class Base2
{
public:
    int m_A=200;
};
class Son:public Base1,public Base2
{
public:
    int m_C = 300;
    int m_D = 400;
};
void test01() {
    Son s;
    cout<<"sizeof Son = "<< sizeof(Son)<
菱形继承

利用虚拟继承解决菱形继承的问题

继承之前加上关键字virtual变为虚继承,被继承的称为虚基类

#include 

using 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;
    //cout<

现在类的内部结构

image-20221016155246546

vbptr
v -virtual
b -base
ptr -pointer
虚基类指针

sheeptuo继承的是sheep和tuo中的指针,指向Animal中的m_Age

总结:

多态

多态分为两类:

静态多态和动态多态的区别

#include 

using namespace std;

class Animal{
public:
    virtual void speak()//加virtual关键字成为虚函数
    {
        cout<<"Animal Speak"<

动态多态满足条件:

  1. 有继承关系
  2. 子类要重写父类的虚函数

动态多态的使用:

底层

vfptr
v -virtual
f -function
ptr -pointer
虚函数(表)指针
指针指向一个表vftable虚函数表
v - virtual
f -function
table
表内部记录虚函数的地址

image-20221016194544062

纯虚函数和抽象类

纯虚函数语法:

virtual 返回值类型 函数名 (参数列表) = 0;
virtual void func() = 0;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

虚构和纯虚析构区别:

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

虚析构函数就是用来解决通过父类指针释放子类对象

#include 

using 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 

using 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()
    {
        cout<<"Intel的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 
#include 

using 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<<"请输入员工姓名:"<>staff->staffName;
        cout<<"请输入员工编号:"<>staff->staffNum;
        cout<<"请输入部门编号:"<>staff->departNum;
        fs<staffName<staffNum<departNum<>choose;
        if (choose==0)//退出管理系统
        {
            cout<<"成功退出!"<

三、 C++提高

1. 模板

#include 

using namespace std;

template 
class Person
{
public:
    Person(NameType name,AgeType age)
    {
        this->m_name = name;
        this->m_age = age;
    }
    void showPerson()
    {
        cout<<"name:"<m_name<m_age< p1("孙悟空",999);
    p1.showPerson();
}

int main()
{
    test01();
}
类模板和函数模板的区别
  1. 类模板没有自动类型推导
  2. 类模板在模板参数列表中可以有默认参数
#include 

using namespace std;
//类模板和函数模板的区别

template //默认参数
class Person
{
public:
    Person(NameType name,AgeType age)
    {
        this->m_name = name;
        this->m_age = 999;
    }
    void showPerson()
    {
        cout<<"name: "<m_name<m_age< p("孙悟空",999);
    p.showPerson();
}
//1. 类模板没有自动类型推到
//2. 类模板在模板参数列表中可以有默认参数
void test02()
{
    Person<> p("猪八戒",999);
}
int main()
{
    test01();
    test02();
}
类模板中成员函数的创建时机

类模板中的成员函数和普通类中成员函数创建时机是有区别的:

#include 

using namespace std;

class Person1
{
public:
    void showPerson1()
    {
        cout<<"Person1 show"<
class MyClass
{
public:
    T obj;
    //类模板中的成员函数在调用时创建
    void func1()
    {
        obj.showPerson1();
    }
    void func2()
    {
        obj.showPerson2();
    }
};

void test01()
{
    MyClass m;
//    m.func1();编译出错,说明函数调用时才会创建成员函数
    m.func2();
}


int main()
{
    test01();
}
类模板对象做函数参数

三种传入方式:

#include 

using namespace std;
//类模板对象做函数参数
template
class Person
{
public:
    Person(T1 name,T2 age)
    {
        this->m_name=name;
        this->m_age=age;
    }
    T1 m_name;
    T2 m_age;
    void personShow()
    {
        cout<<"姓名: "<m_name;
        cout<<"年龄: "<m_age< &p)
{
    p.personShow();
}
void test01()
{
    Person p("孙悟空",10);
    printPerson1(p);
}

//2. 参数模板化
template
void printPerson2(Person &p)
{
    p.personShow();
    cout<<"T1的类型为:"< p("猪八戒",20);
    printPerson2(p);
}
//3. 整个类模板化
template
void printPerson3(T &p)
{
    p.personShow();
    cout<<"T的类型为: "<< typeid(T).name()< p("唐僧",30);
    printPerson3(p);
}
int main()
{
    test01();
    test02();
    test03();
}
类模板与继承
#include 

using namespace std;

template
class Base
{
    T m;
};
class Son:public Base//继承时必须要知道父类T的类型才能继承给子类
{

};


void test01()
{
    Son s1;
}
#include 

using namespace std;

template
class Base
{
    T m;
};
class Son1:public Base
{

};
template
class Son2:public Base
{
public:
    Son2()
    {
        cout<<"T1的数据类型为: "< s2;
}

int main()
{
    test01();
    test02();
}
模板类中函数的类外实现
#include 

using namespace std;
//类模板函数的类外实现
template
class Person
{
public:
    T1 m_name;
    T2 m_age;
    Person(T1,T2);
    void showPerson();
};
template
Person::Person(T1 name, T2 age)
{
    this->m_name=name;
    this->m_age=age;
}
template
void Person::showPerson()
{
    cout<<"姓名: "<m_name<<"年龄: "<m_age< 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 
#include 
#include 

using namespace std;
void myPrint(int val)
{
    cout<<val<<endl;
}
void test01()
{
    //创建了一个vector容器
    vector<int> v;
    
    //想容器中插入数据
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);
    
    //通过迭代器访问容器中的数据
    vector<int>::iterator itBegin = v.begin();//其实迭代器
    vector<int>::iterator itEnd = v.end();//结束迭代器,指向容器最后一个元素的下一个位置
   
    //第一种遍历方式
    while(itBegin!=v.begin())
    {
        cout<< *itBegin <<endl;
        itBegin++;
    }
    //第二种遍历方式
    for(vector<int>::iterator it=v.begin();it!=v.end;it++)
    {
        cout<<*it<<endl;
    }
    //第三种遍历方式
    for_each(v.begin(),v.end(),myPrint);//回调函数
}
int main()
{
    test01();
}

vector是STL中最常用的一种数据结构

特点:

vector的构造函数

#include 
#include 
using namespace std;

void printVector(vector<int>&v)
{
    for(const int & n : v)
    {
        cout<<n<<" ";
    }
    cout<<endl;
}
void test01()
{
    vector<int> v1;//默认构造
    for(int i=0;i<10;i++)
    {
        v1.push_back(i);
    }
    cout<<"v1"<<endl;
    printVector(v1);

    vector<int> v2(v1.begin(),v1.end());//将v1迭代器区间的内容给v2
    cout<<"v2"<<endl;
    printVector(v2);

    vector<int> v3(10, 'a');//n个elem
    cout<<"v3"<<endl;
    printVector(v3);

    vector<int> v4(v3);//拷贝构造
    cout<<"v4"<<endl;
    printVector(v4);
}

int main()
{
    test01();
}

vector赋值

函数原型:

示例:

void test01()
{
    vector v1;
    for(int i=0;i<10;i++)
    {
        v1.push_back(i);
    }
    vector v2 = v1;//等号操作符进行赋值

    vector v3;//将左闭右开区间赋值给本身
    v3.assign(v1.begin(),v1.end());

    vector v4;
    v4.assign(10,1);//n个elem

}

vector容器的容量和大小

函数原型

void test01()
{
    vector v1;
    for(int i=0;i<10;i++)
    {
        v1.push_back(i);
    }
    cout<<"v1是否为空"<

vector的插入和删除

函数原型

vector数据的存取

函数原型

vector互换容器

函数原型

作用1:收缩内存

void test01()
{
    vector v;
    for (int i = 0; i < 10000; ++i) {
        v.push_back(i);
    }
    cout<<"v的容量"<(v).swap(v);

    cout<<"交换后v的容量"<

vector预留空间

作用:提前预留空间,减少动态扩展容量时的扩展次数

函数原型

reserve只预留空间不初始化数据

如果vector要存放的数据比较多,可以直接预留足够的空间,不用一次次地动态扩展,影响性能

void test01()
{
    vector<int> 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++;
        }
    }
    cout<<"v1动态扩展的次数:"<<num<<endl;


    vector<int> 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++;
        }
    }
    cout<<"v2动态扩展的次数:"<<num2<<endl;
}
string容器

string是C++中用于管理字符的一个类

本质上字符在string类中是char *类型的变量,只不过被封装成了一个类,这个类中重载了很多运算符,使其像个数组一样。下面总结了一些string类的函数和重载的运算符

string的构造函数

string()默认构造

string(const char* s)字符串构造

string(const string& s)拷贝构造

string(int num, char c)数值*字符构造

#include 

using 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');//数量*字符
    cout<<"s1 = "<<s1<<endl;
    cout<<"s2 = "<<s2<<endl;
    cout<<"s3 = "<<s3<<endl;
    cout<<"s4 = "<<s4<<endl;
    cout<<"s5 = "<<s5<<endl;
}

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使函数变为调用时不可修改类内部数据的函数

示例:

void test01()
{
    string str = "abcdefef";
    int pos = str.find("ef");
    cout<<pos<<endl;//4
    int pos2 = str.rfind("ef");
    cout<<pos2<<endl;//6
    
    str.replace(1,3,"1111");
    cout<<str<<endl;//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)
	{
		cout<<"str1=str2"<

string字符存取

test01()
{
	string str = "abcdefg";
    //第一种方式
	for(int i=0;i<str.size();i++)
	{
		cout<<str[i]<<endl;
	}
	//第二种方式
	for(int i=0;i<str.size();i++)
	{
		cout<<str.at(i)<<endl;
	}
}

string插入和删除

函数原型

void test01()
{
    string str1 = "hello";
    string str2 = "world";
    str1.insert(5," ");
    cout<<str1<<endl;//hello空格
    str1.insert(6, str2);
    cout<<str1<<endl;//hello world
}

string子串

函数原型:

示例:

void test01()
{
    string str1 = "hello";

    string str2 = str1.substr(0,2);
    cout<<str2<<endl;
}
deque容器

deque是一个双端数组,可对头端进行插入

deque与vector的区别:

image-20221123162825275

deque内部原理:

image-20221123163056518

构造函数

函数原型:

deque赋值

与vector类似

函数原型:

deque大小

函数原型:

deque插入和删除

函数原型:

两端插入操作

指定位置插入删除:

deque数据存取

函数原型:

deque排序操作

利用算法实现deque内部数据的排序

#include

函数原型:

void test01()
{
    deque 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 30
    printDeque(deq);
    sort(deq.begin(),deq.end());
    printDeque(deq);//10 20 30 100 200 300
}

image-20221123170301314

#include 
#include 
#include 
#include 
#include 

using namespace std;

class Person;
void createPlayer(vector&);

void setScore(vector&);

void showScores(vector);

class Person
{
public:
    explicit Person(string name, double score=0): m_name(std::move(name)), m_score(score){}
    string m_name;
    double m_score;
};
int main()
{
    //1. 创建一个vector容器存放5个选手
    vector v;
    createPlayer(v);
    //测试
//    for(auto p:v)
//    {
//        cout<& v) {
    for(const auto & it : v)
    {
        cout<<"姓名:"<& v) {
    srand((unsigned int)time(nullptr));//随机数种子
    for(auto & it : v)
    {
        deque 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(vector& 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

image-20221127171258461

构造函数

赋值

数据存取

大小操作

queue容器

image-20221128101847399

构造函数

赋值操作

数据存取

大小操作

list容器

image-20221128103224913

优点:

缺点:

构造函数

赋值和交换

大小操作

list插入和删除

函数原型:

list 数据存取

功能描述:

函数原型:

list容器中不可以通过[]或者at方式访问数据

list 反转和排序

功能描述:

函数原型:

void printList(const list& L) {

	for (list::const_iterator it = L.begin(); it != L.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

bool myCompare(int val1 , int val2)
{
	return val1 > val2;
}

//反转和排序
void test01()
{
	list 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构造和赋值

功能描述:创建set容器以及赋值

构造:

赋值:

插入:

并且元素在插入之后会自动排序

大小和交换

功能描述:

函数原型:

set插入和删除

功能描述:

函数原型:

set查找和统计

功能描述:

函数原型:

set和multiset区别

学习目标:

区别:

#include 

using namespace std;

int main()
{
    set s;
    pair::iterator,bool> ret = s.insert(10);
    if(ret.second){
        cout<<"第一次插入成功"< ms;
    ret = ms.insert(10);
    if(ret.second){
        cout<<"multiset插入成功"<

pair的使用

pair是成对存在的一组数据

对组的创建

访问

set容器排序规则

默认排序规则从小到大,利用仿函数改变规则

使用仿函数

#include 
#include 
using namespace std;
class MyCompare{
public:
    bool operator()(const int&v1,const int&v2){
        return v1>v2;
    }
};
int main(){
    set s;
    s.insert(1);
    s.insert(2);
    s.insert(3);
    for(const auto&i:s){
        cout<

set容器使用自定义数据类型插入时会报错,所以需要指定排序规则

#include 
#include 
#include 

using namespace std;
class Person{
public:
    Person(string name,int age){
        this->name = std::move(name);
        this->age=age;
    }
    string name;
    int age;
};
class comparePerson{
public:
    bool operator()(const Person& p1, const Person& p2){
        return p1.age>p2.age;
    }
};

int main(){
    set s;
    s.insert(Person("刘备",12));
    s.insert(Person("关羽",21));

    for(const auto&i:s){
        cout<<"姓名:"<
map容器

map基本概念

简介:

本质:

优点:

map和multimap区别

map构造和赋值

功能描述:

函数原型:

构造:

赋值:

int main(){
    map mp;
    
    map.insert(pair(1,10));
    map.insert(pair(3,30));
    map.insert(pair(2,20));
    
    for(const auto& i:mp){
        cout<<"key值="<

map大小和交换

函数原型:

map插入和删除

功能描述:

函数原型:

map查找和统计

功能描述:

函数原型:

map容器排序

学习目标:

主要技术点:

#include 
#include 
using namespace std;
class MyCompare{
    public:
    bool operator()(const int&v1,const int&v2){
        return v1>v2;
    }
}

test01(){
    map mp;
    mp.insert(pair(1,10));
    mp.insert(pair(4,40));
    mp.insert(make_pair(2,20));
    mp.insert(make_pair(3,30));
    
    for(const auto&i:mp){
        cout<<"key="<
函数对象

概念:

本质:

函数对象(仿函数)是一个,不是一个函数

class MyAdd{
public:
    int operator()(int& v1,int& v2){
        status++;
        return v1+v2;
    }
	int status;
};

一元谓词和二元谓词

#include 
#include 
#include 
using namespace std;

class MyCompare{
public:
    bool operator()(const int&v1,const int&v2){
        return v1>v2;
    }
};
bool mycompare(const int& v1,const int&v2){
    return v1>v2;
}

int main(){
    vector 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){
        cout<
STL内建函数对象

分类:

这些仿函数所产生对象的用法和一般函数一样

使用内建函数对象需要引入头文件#include

算数仿函数

功能描述:

仿函数原型:

void test01(){
    negaten;
    cout<p;
    cout<

关系仿函数

功能描述:

仿函数原型:

void test01(){
    vector 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(),greater());
}

逻辑仿函数

功能描述:

函数原型:

#include 
#include 
#include 
#include 
using namespace std;
void test01()
{
    vector v;//如果类型为int型,则除0以外的都是true
    v.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中,并执行逻辑非运算
    vector 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;
}
  • 相关阅读:
    mongoose 源码解读(一)
    稳压器【TPS6283810YFPR 3A】汽车类、TPS629203QDRLRQ1,TPS74550PQWDRVRQ1采用小型6 引脚 WSON 封装。
    Git如何上传代码至GitHub
    windows2019下安装mysql 8.0.29及解决服务无法启动
    【html5期末大作业】基于HTML仿QQ音乐官网网站
    数字化产业研究报告
    [NET毕业设计源码]基于NET实现的旅游景点推荐系统[包运行成功]
    行人检测综述 之 精华提取——图表与挑战
    .NET from-where-select用法
    【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 2 期 | 精品题解
  • 原文地址:https://blog.csdn.net/weixin_51954217/article/details/131645998