c++笔记

c++入门

一、数组

1.1概述

所谓数组,就是一个集合,里面存放了相同类型的数据元素

特点1:数组中的每个数据元素都是相同数据类型

特点2:数组是由连续的内存位置组成的

1.2数组
1.2.1一维数组定义三种方式

1.数据类型 数组名[ 数组长度 ];

int arr[5];

arr[0]=10;arr[1]=10;arr[2]=10;arr[3]=10;arr[4]=10;

2.数据类型 数组名[ 数组长度 ] = {值1,值2,...};

int arr[5]={10,20,30,40,50};

3.数据类型 数组名[ ]={ 值1,值2,...};

int arr[]={10,20,30,40,50};

一维数组数组名

可以统计整个数组在内存中的长度

可以获取数组在内存中的首地址

1.2.2二维数组定义四种方式

1.数据类型 数组名 【行数】【列数】

int arr[2][3];

int a[0][0]=1;int a[0][1]=1;int a[0][2]=1;

int a[1][0]=1;int a[1][1]=1;int a[1][2]=1;

2.数据类型 数组名 【行数】【列数】={{ 数据1,数据2}, {数据3,数据4}};

int arr[2][3]={ {1,2,3},{4,5,6}};

3.数据类型 数组名 【行数】【列数】={数据1,数据2, 数据3,数据4};

int arr[2][3]={ 1,2,3,4,5,6 };

4.数据类型 数组名 【 】【列数】={数据1,数据2, 数据3,数据4};

int arr[ ][3]={ 1,2,3,4,5,6 };

二维数组数组名

查看二维数组所占内存空间

获取二维数组首地址

二、函数

将一段经常使用的代码封装起来,减少重复代码

一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能

2.1函数的定义

返回值类型 函数名 参数列表 函数体语句 return表达式

值传递:当进行值传递时,函数的形参发生改变,并不会影响实参

2.2函数的声明

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

2.3函数的分文件编写

作用:让代码结构更加清晰

函数分文件编写一般有4个步骤:

1.创建后缀名为.h的头文件

2.创建后缀名为.cpp的源文件

3.在头文件中写函数的声明

4.在源文件中写函数的定义

2.4函数提高

在c++中,函数的形参列表中的形参是可以有默认值的

语法:返回值类型 函数名 ( 参数=默认值 ){}

注意:如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值

如果函数声明有默认参数,函数实现就不能有默认参数,声明和实现只能有一个有默认参数

2.4.1函数占位参数

c++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:返回值类型 函数名(数据类型){}

2.4.2函数重载

函数名可以相同,提高复用性

函数重载满足条件:

1.同一个作用域下

2.函数名称相同

3.函数参数类型不同或者个数不同或者顺序不同

函数的返回值时不可以作为函数重载的条件

2.4.3函数重载注意事项

1.引用作为重载的条件

2.函数重载碰到默认参数

当函数重载碰到默认参数,会出现二义性,尽量避免这种情况

三、指针

作用:可以通过指针间接访问内存

内存编号是从0开始记录的,一般用十六进制数字表示

可以利用指针变量保存地址o

3.1指针变量

指针变量定义语法:数据类型 *变量名

指针记录的是地址,在指针前面加一个*表示解引用,找到指针指向的内存中的数据

3.2空指针和野指针

int *p=NULL;

空指针:指针变量指向内存中编号为0的空间

用途:初始化指针变量

注意:空指针指向的内存是不可以访问的

野指针:指针变量指向非法的内存空间

总结:空指针和野指针都不是我们申请的空间,因此不要访问

3.2const修饰指针

const修饰指针有三种情况:

1.const修饰指针——常量指针

特点:指针的指向可以修改,但是指针指向的值不可以修改

2.const修饰常量——指针常量

特点:指针的指向不可以修改,但是指针指向的值可以修改

3.const既修饰指针,又修饰常量

特点:指针的指向和指针指向的值都不可以修改

int a=10;

int b=10;

//const修饰指针——常量指针

const int *p=&a;

*p=20;//错误

p=&b;//正确

//const修饰常量——指针常量

int * const p2=&a;

*p=20;//正确

p=&b;//错误

//const既修饰指针,又修饰常量

const int *const p3=&a;

*p=20;//错误

p=&b;//错误

3.3指针和数组

利用指针访问数组中的元素

int arr[10]={1,2,3,4,5,6,7,8,9,10};

int *p=arr//arr就是数组的首地址

cout<<"利用指针访问第一个元素:"<<*p<<endl;

p++;

cout<<"利用指针访问第二个元素:"<<*p<<endl;

3.4指针和函数

地址传递:利用指针作函数参数,可以修改实参的值

void swap(int *p1,int *p2){

int temp=*p1;

*p1=*p2;

*p2=temp;

}

int main(){

int a=10;

int b=10;

swap(&a,&b);

}

值传递不会改变实参,地址传递会改变实参

四、结构体

结构体属于用户自定义的数据类型,允许用户存储不同的数据类型

4.1结构体的定义和使用

语法:struct 结构体名{结构体成员列表};

通过结构体创建变量的方式有三种:

struct 结构体名 变量名

struct 结构体名 变量名={成员1值,成员2值...}

定义结构体时顺便创建变量

struct student{

string name;

int age;

int score;

};

4.2结构体数组

//定义结构体

struct Student{

string name;

int age;

int score;

};

int main(){

//创建结构体数组

struct Student stuArray[8]={

{"张三",18,100},

{“李四”,28,90},

{"王五",38,66}

};

//给结构体数组中的元素赋值

stuArray[2].name="赵六";

//遍历结构体数组

for(int i=0;i<3;i++){

cout<<"姓名:"<<stuArray[i].name<<"年龄:"<<stuArray[i].age<<"分数:"<<stuArray[i].score<<endl;

}

}

4.3结构体指针

1.创建学生的结构体变量

student s={"张三",18,100};

2.通过指针指向结构体变量

student *p=&s;

3.通过指针访问结构体变量中的数据

cout<<"姓名:"<<p->name<<"年龄:"<<p->age<<"分数:"<<p->score<<endl;

4.4结构体嵌套结构体

struct Teacher{

string name;

int age;

struct student stu;

};

int main(){

Teacher T;

T.name="老王";

T.age=50;

T.stu.name="小王";

T.stu.age=20;

}

结构体中const使用场景

将函数中的形参改成指针可以减少内存空间,但是为了保证函数中修改的量在实参中保持不变,因此可以在形参指针上加上const,一旦 有修改 的操作就会报错,可以防止误操作。

void print(const student *stu){//加const防止误操作

stu->age=100;//操作失败,因为加了const修饰

cout<<stu->name...

}

int main(){

student stu={"张三",18,100};

print(&stu);

}

五、程序的内存模式

c++程序在执行时,将内存大方向分为四个区域

1.代码区:存放函数体的二进制代码,由操作系统进行管理

2.全局区:存放全局变量和静态变量以及常量

3.栈区:由编译器自动分配释放,存放函数的参数值,局部变量等,栈区的数据在函数执行完后自动释放

注意:不要返回局部变量的地址 ,栈区开辟的数据由编译器自动释放

4.堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

利用new关键字 可以将数据开辟到堆区

int *p=new int(10);

5.1 new操作符

c++中利用new操作符在堆区开辟数据,堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete

语法:new 数据类型

利用new创建的数据,会返回该数据对应的类型的指针

释放数组的时候,需要 加[] 如:delete [] arr;

5.2 引用

作用:给变量起别名

语法:数据类型 &别名=原名

注意事项:引用必须初始化,且初始化后不可以改变

5.2.1引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

引用传递:形参会修饰实参

int swap(int &a,int &b)

地址传递形参会修饰实参

int swap(int *a,int *b)

值传递形参不会修饰实参

int swap(int a,int b)

5.2.2引用做函数返回值

1.不要返回局部变量的引用

2.函数的调用可以作为左值

5.2.3引用的本质

本质:引用的本质在c++内部实现是 一个指针常量

//发现是引用,转换为int* const ref = &a;

void func(int & ref){

ref = 100;//ref是引用,转换为*ref=100

}

int main(){

int a=10;

//自动转换为 int *const ref = &a;指针常量是指针指向不可改,也说明为什么引用不可更改

int &ref=a;

ref=20;

cout <<"a:"<<a<<endl;

cout<<"ref:"<<ref<<endl;

func(a);

}

5.2.4常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

六、类和对象

6.1封装

将属性和行为作为一个整体

语法:class 类名{ 访问权限:属性 / 行为};

//设计一个圆类,求圆的周长

const double PI=3.14;

class Circle{

//访问权限

public:

//属性

int m_r;

//行为

//获取圆的周长

double calculateZC(){

return 2*PI*m_r;

}

};

int main(){

Circle c1;

cl.m_r=10;

cout<<"圆的周长为:"<<c1.calculateZC()<<endl;

}

6.1.1封装—访问权限

类在设计时,可以把属性和行为放在不同的权限下加以控制

访问权限有三种:

1.public 公共权限:类内可以访问,类外也可以访问

2.protected 保护权限:类内可以访问,类外不可以访问 儿子可以访问父亲中的保护内容

3.private 私有权限:类内可以访问,类外不可以访问 儿子不可以访问父亲的私有内容

6.1.2struct和class的区别

在c++中struct和class唯一的区别就在于默认的访问权限不同

struct默认权限为公共

class默认权限为私有

6.1.3成员属性私有化

将成员属性设置为私有,可以自己控制读写权限

对于写权限,可以检测数据的有效性

class Person{

public:

//设置姓名

void setName(string name){

m_name=name;

}

//获取姓名

string getName(string name){

return m_name;

}

private:

string name;

int age;

}

6.1.4练习

1)

//立方体类 class Crub { public: //获取长 int getL() { return m_L; }

//设置长
void setL(int l) {
    m_L = l;
}
//获取高
int getH() {
    return m_H;
}
//设置高
void setH(int h) {
    m_H = h;
}
    //获取宽
    
//获取宽
int getW() {
    return m_W;
}
//设置宽
void setW(int w) {
    m_W = w;
}
int caculateS(int m_L, int m_H, int m_W) {
    return 2 * m_L * m_H + 2 * m_L * m_W + 2 * m_W * m_H;
}
int caculateV(int m_L, int m_H, int m_W) {
    return m_L * m_H * m_W;
}
//判断两个立方体是否相等
bool isEqual(Crub& c) {
    if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH()) {
        return true;
    }
    else
        return false;
}

private: int m_L; double m_H; double m_W; `};

`int main()

{

Crub c1;

c1.setH(10);

c1.setL(10);

c1.setW(10);

Crub c2;

c2.setH(10);

c2.setL(10);

c2.setW(10);

bool ret = c1.isEqual(c2);

if (ret)

{

cout << "c1与c2是相等的" << endl;}

2)点与圆的位置关系

//点类 class Point { public: int getPx() { return px; } void setPx(int x) { px = x; } int getPy() { return py; } void setPx(int y) { px = y; } private: int px; int py; }; //圆类 class Circle { public: Point getCenter(){ return m_Center; } void setCenter(Point center){ m_Center = center; } int getR() { return m_R; } void setR(int r) { m_R = r; } //判断点与圆的位置,在圆内为1,在圆上为0,在圆外为-1 void locateaa(Point &p){ int distance = (p.getPx() - m_Center.getPx()) * (p.getPx() - m_Center.getPx()) + (p.getPy() - m_Center.getPy()) * (p.getPy() - m_Center.getPy()); int rDistance = m_R * m_R; if (distance > rDistance) { cout << "点在圆外" << endl; } } private: Point m_Center;//圆心 int m_R; };

6.2对象的初始化和清理
6.2.1构造函数和析构函数

构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用

析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

构造函数语法:类名(){}

1.构造函数,没有返回值也不写void

2.函数名称与类名相同

3.构造函数可以有参数,因此可以发生重载

4.程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次

class Person{

public:

//无参构造

Person (){

cout<<"Person构造函数的调用"<<endl;

}

//有参构造

Person (int a){

age=a;

cout<<"Person构造函数的调用"<<endl;

}

//拷贝构造函数

Person(const Person &p){

//将传入的人身上所有属性拷贝过来

age=p.age;

}

}

//调用

void test(){

//1.括号法

Person p1;//默认构造函数调用

Person p2(10);//有参构造函数

Person p3(p2);//拷贝构造函数

}

析构函数语法:~类名(){}

1.析构函数,没有返回值也不写void

2.函数名称与类名相同

3.析构函数不可以有参数,因此不可以发生重载

4.程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次

~Person(){

cout<<"Person的析构函数调用"<<endl;

}

6.2.2 静态成员

静态成员变量:所有对象共享同一份数据;在编译阶段分配内存;类内声明,类外初始化

静态成员函数:所有对象共享同一个函数;静态成员函数只能访问静态成员变量

示例:静态成员变量

class Person{

public:

static int m_A;

private:

static int m_B;

}

//类内声明,类外初始化

int Person::m_A=100;

int Person::m_B=200;

void test(){

//静态成员变量两种访问方式

//1.通过对象

Person p1;

p1.m_A=100;

cout<<"p1.m_A= "<<p1.m_A<<endl;

Person p2;

p2.m_A=200;

cout<<"p1.m_A= "<<p1.m_A<<endl;//共享同一份数据

cout<<"p2.m_A= "<<p2.m_A<<endl;

//2.通过类名

cout<<"m_A= "<<Person::m_A<<endl;

//cout<<"m_B= "<<Person::m_B<<endl;私有权限访问不到

}

6.3 c++对象模型和this指针
6.3.1 成员变量和成员函数分开存储

在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上

6.3.2this指针概念

this指针指向被调用的成员函数所属的对象

this指针不可以修改指针的指向的,但this指针指向的值是可以修改的

this指针的用途:

当形参和成员变量同名时,可以用this指针来区分

class Person{

public:

Person(int age){

this->age=age;

}

Person& PersonAddAge(Person &p)zz{

this->age+=p.age;

//在类的非静态成员函数中返回对象本身,可使用return *this

return *this;

}

int age;

}

void test(){

Person p1(10);

Person p210);

//链式编程思想

p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

}

6.3.3 空指针访问成员函数

c++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针,

如果用到this指针,需要加以判断以保证代码的健壮性

6.3.4 const修饰成员函数

常函数:成员函数后加const成为常函数,修饰的是this指向,让指针指向的值也不可以修改

void showPerson() const{}

常函数内不可以修改成员属性,成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:声明对象前加const称该对象为常对象

常对象只能调用常函数

常对象不可以调用普通成员函数,因为普通成员函数可以修改属性

6.3.5 友元

友元的目的就是让一个函数或者类访问另一个类中的私有成员

友元的关键字为friend

友元的三种实现:全局函数做友元,类做友元,成员函数做友元。

6.4运算符重载

对已有的运算符重新定义,赋予另一种功能,以适应不同的数据类型

6.4.1 加号运算符重载

#include<iostream> #include<string> using namespace std;

//实现两个自定义数据类型相加的运算

class Person { public: ``

//1、成员函数实现+运算符重载
//Person operator+(Person& p) {
//  Person temp;
//  temp.m_A = this->m_A + p.m_A;
//  temp.m_B = this->m_B + p.m_B;
//  return temp;
​
//}

` int m_A; int m_B;};

`//2、全局函数重载+号 Person operator+(Person &p1, Person &p2) { Person tmp; tmp.m_A = p1.m_A + p2.m_A; tmp.m_B = p1.m_B + p2.m_B; return tmp; }`

int main() { test1(); system("pause"); return 0; }

6.4.2 左移运算符重载

#include<iostream> #include<string> using namespace std;

//左移运算符重载 class Person { friend ostream& operator<<(ostream& out, Person& p); public: Person(int a, int b) { m_A = a; m_B = b; } private: int m_A; int m_B;

}; //只能利用全局函数重载左移运算符 ostream & operator<<(ostream& out, Person& p) { out << "m_A=" << p.m_A << " m_B=" << p.m_B; return out; } void test() { Person p(10,10); // p.m_A = 10; // p.m_B = 10; cout << p <<endl;

} int main() { test(); }

重载左移运算符配合友元可以实现输出自定义数据类型

6.4.3递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

#include<iostream> #include<string> using namespace std;

//递增运算符重载 class MyInteger { friend ostream& operator<<(ostream& out, MyInteger myint); public: MyInteger() { m_Num = 0; }

//重载前置++运算符 返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++() {
    m_Num++;
    return *this;
}
​
//重载后置++运算符
//void operator++(int)  int代表占位参数,可以用于区分前置和后置递增
MyInteger operator++(int) {
    //先记录当时结果
    MyInteger temp = *this;
    //后递增
    m_Num++;
    //最后将记录结果做返回
    return temp;
}

private: int m_Num = 0; `};` #include<iostream> #include<string> using namespace std;

//递增运算符重载 class MyInteger { friend ostream& operator<<(ostream& out, MyInteger myint); public: MyInteger() { m_Num = 0; }

//重载前置++运算符 返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++() {
    m_Num++;
    return *this;
}
​
//重载后置++运算符
//void operator++(int)  int代表占位参数,可以用于区分前置和后置递增
MyInteger operator++(int) {
    //先记录当时结果
    MyInteger temp = *this;
    //后递增
    m_Num++;
    //最后将记录结果做返回
    return temp;
}

private: int m_Num = 0; }; //重载左移运算符 ostream& operator<<(ostream& out,MyInteger myint) { cout << myint.m_Num; return cout; } void test1() { MyInteger myint; cout << ++myint << endl; } void test2() { MyInteger myint; cout << myint- << endl; } int main() { test1(); test2(); }

前置递增返回引用,后置递增返回值

6.4.4赋值运算符重载

#include<iostream> using namespace std;

//赋值运算符重载 class Person {

public: Person(int age) { m_Age = new int(age); } //重载赋值运算符 Person& operator=(Person &p) { //编译器是提供浅拷贝 //m_Age=p.m_Age;

    //应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
    if (m_Age != NULL) {
        delete m_Age;
        m_Age = NULL;
    }
    //深拷贝
    m_Age = new int(*p.m_Age);
​
    //返回对象本身
    return *this;
}
int* m_Age;

}; void test() { Person p1(18); Person p2(20); Person p3(30); p3 = p2 = p1; cout << "p1的年龄为:" << *p1.m_Age << endl; cout << "p2的年龄为:" << *p2.m_Age << endl; cout << "p3的年龄为:" << *p3.m_Age << endl; }

int main() { test(); }

6.4.5关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

#include<iostream> using namespace std;

//重载关系运算符 class Person { public: Person(string name, int age) { m_Name = name; m_Age = age; } //重载== bool operator==(Person& p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } else return false; }

//重载!=
bool operator==(Person& p) {
    if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
        return false;
    }
    else
        return true;
}
string m_Name;
int m_Age;

}; void test() { Person p1("Tom", 18); Person p2("Tom", 18); if (p1 == p1) { cout << "p1和p2是相等的"<<endl; } else { cout << "p1和p2是不相等的" << endl; } } int main() {

}

6.5继承

减少重复代码

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

子类也称为派生类、父类也称为基类

#include<iostream> #include<string> using namespace std;

//继承实现页面

//公共页面类 class BasePage { public: void header() { cout << "首页、公共课、登录、注册...(公共头部)" << endl; } void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; } void left() { cout << "Java、python、c++\...(公共分类列表)" << endl; }

}; //java页面 class Java :public BasePage { public: void context() { cout << "Java学科视频" << endl; } }; //python页面 class Python :public BasePage { public: void context() { cout << "Python学科视频" << endl; } }; //c++页面 class Cpp :public BasePage { public: void context() { cout << "c++学科视频" << endl; } };

int main() { `}`

6.5.1继承方式

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

继承方式一共有三种:公共继承、保护继承、私有继承

6.5.2继承中的对象模型

父类中的所有非静态成员属性都会被子类继承下去,父类中的私有成员函数是被编译器给隐藏了,因此是访问不到的,但是确实被继承下去了。

利用开发人员命令提示工具查看对象模型:

1、跳转盘符

2、跳转文件路径到当前cpp文件下

3、查看命名:cl /d reportSingleClassLayout类名 文件名

6.5.3继承中构造和析构顺序

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

继承中构造和析构顺序:先构造父类,再构造子类,析构的顺序与构造的顺序相反

6.5.4继承同名成员处理方式

子类对象可以直接访问到子类中的同名成员;

子类对象加作用域可以访问到父类同名成员;

当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。

6.5.5 多继承语法

c++允许一个类继承多个类

语法:class 子类:继承方式 父类1,继承方式 父类2...

多继承可能会引发父类中有同名成员出现,需要加作用域区分

c++实际开发中不建议用多继承

6.5.6菱形继承问题

#include<iostream> #include<string> using namespace std;

class Animal { public: int m_Age; }; //继承前加virtual关键字后,变为虚继承 //此时公共的父类Animal称为虚基类 class sheep :virtual public Animal {}; class Tuo :virtual public Animal {}; class sheepTuo:public sheep,public Tuo{};

void test() { sheepTuo st; st.sheep::m_Age = 100; st.Tuo::m_Age = 200;

cout << "st.sheep::m_Age=" << st.sheep::m_Age << endl;
cout << "st.Tuo::m_Age=" << st.Tuo::m_Age << endl;
cout << "st.sheepTuo::m_Age=" << st.sheepTuo::m_Age << endl;

} int main() { test(); }

菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义

利用虚继承可以解决菱形继承问题

6.6多态

多态分为两类:

静态多态:函数重载和运算符重载属于静态多态,复用函数名

动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

静态多态的函数地址早绑定 - 编译阶段确定函数地址

动态多态的函数地址晚绑定 - 运行阶段确定函数地址

动态多态满足条件:1、有继承关系;2、子类重写父类的虚函数

动态多态使用:父类的指针或者引用执行子类对象

重写:函数返回值类型,函数名,参数列表完全一致称为重写

6.6.1抽象类和纯虚函数

纯虚函数语法:virtual 返回值类型 函数(参数列表)=0;

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

抽象类的特点:

无法实例化对象

子类必须重写抽象类中的纯虚函数, 否则也属于抽象类

6.6.2虚析构和纯虚析构

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

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

虚析构和纯虚析构共性:

可以解决父类指针释放子类对象

都需要有具体的函数实现

虚析构和纯虚析构区别:

如果是纯虚析构,改类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名()=0;

类名::~类名(){}

总结:

1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3.拥有纯虚析构函数的类也属于抽象类

七、文件

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化

c++中对文件操作需要包含头文件<fstream>

文件类型:

1.文本文件:文件以文本的ASCLL码形式存储在计算机中

2.二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们

操作文件的三大类:

1.ofstream:写操作

2.ifstream:读操作

3.fstream:读写操作

7.1文本文件
7.1.1写文件

写文件步骤:

1.#include<fstream> 包含头文件

2.ofstream ofs; 创建流对象

3.ofs.open("文件路径",打开方式);

4.ofs<<"写入的数据";

5.ofs.close(); 关闭文件

文件的打开方式:

ios::in 为读文件而打开文件

ios::out 为写文件而打开文件

ios::ate 初始位置:文件尾

ios::app 追加方式写文件

ios::trunc 如果文件存在先删除,再创建

ios::binary 二进制方式

注意:文件打开方式可以配合使用,利用|操作符

例如:用二进制方式写文件 ios::binary | ios::out

7.1.2读文件

读文件步骤:

1.#include<fstream> 包含头文件

2.ofstream ifs; 创建流对象

3.ofs.open("文件路径",打开方式);

4.四种方式读取

5.ifs.close(); 关闭文件

#include<iostream> #include<string> #include<fstream> using namespace std;

void test() { ofstream ofs; ofs.open("text.txt", ios::out); ofs << "姓名:张三" << endl; ofs.close(); } void test1() { ifstream ifs; ifs.open("text.txt", ios::in); if (!ifs.is_open()) { cout << "文件打开失败" << endl; return; } //读数据 //第一种 char buf[1024] = { 0 }; while (ifs >> buf) { cout << buf << endl; }

//第二种
char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf))) {
    cout << buf << endl;
}
​
//第三种
string buf;
while (getline(ifs, buf)) {
    cout << buf << endl;
}
ifs.close();

} int main() { test(); test1(); }

7.2二进制文件

以二进制的方式对文件进行读写操作

打开方式要指定为ios::binary

7.2.1写文件

二进制文件写文件主要利用流对象调用成员函数write

函数原型:ostream& write(const char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数

7.2.2读文件

二进制文件写文件主要利用流对象调用成员函数read

函数原型:ostream& read(const char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数

#include<iostream> #include<string> #include<fstream> using namespace std; //二进制文件 写文件 class Person { public: char m_Name[64];//姓名 int m_Age;//年龄 }; void test() { // ofstream ofs.("person.txt", ios::out | ios::binary); ofstream ofs; ofs.open("person.txt", ios::out | ios::binary); Person p = { "张三",18 }; ofs.write((const char *)&p,sizeof(Person)); ofs.close(); } void test1() { ifstream ifs; ifs.open("person.txt", ios::in | ios::binary); if(!ifs.is_open()) { cout << "文件打开失败" << endl; } //读文件 Person p; ifs.read((char*)&p, sizeof(Person)); cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl; } int main() { test(); test1(); }

c++提高

一、模板

1.1模板的概念

c++另一种编程思想称为泛型编程,主要利用的技术就是模板

c++提供两种模板机制函数模板和类模板

1.2函数模板

作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表

1.2.1函数模板语法

语法:template<typename T>

函数声明或定义

解释:

template :声明创建模板

typename :表明其后面的符合是一种数据类型,可以用class代替

T :通用的数据类型,名称可以体会,通常为大写字母

//函数模板 template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; }

void test() { int a = 10; int b = 20; //两种方式使用函数模板 //1、自动类型推导 mySwap(a, b); //2、显示指定类型 mySwap<int>(a, b); }

1.2.2函数模板注意事项

自动类型推导,必须推导出一致的数据类型T,才可以使用

模板必须要确定出T的数据类型,才可以使用

例题:数组排序

#include<iostream> #include<string> using namespace std;

//函数模板 template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } //选择排序函数 template<class T> void mySort(T arr[],int len) { for (int i = 0;i < len;i++) { int max = i; for (int j = i + 1;j < len;j++) { if (arr[max] < arr[j]) { max = j; } } if (max != i) { mySwap(arr[max], arr[i]); } } } //打印函数 template<class T> void printArray(T arr[], int len) { for (int i = 0;i < len;i++) { cout << arr[i] << " "; } cout << endl; } void test() { char charArr[] = "badcfe"; int num = sizeof(charArr) / sizeof(char); mySort(charArr, num); printArray(charArr, num);

int intArr[] = { 7,3,3,5,12,5,2 };
int length = sizeof(intArr) / sizeof(int);
mySort(intArr, length);
printArray(intArr, length);

} int main() { test(); }

1.2.3普通函数与函数模板区别

1、普通函数调用可以发生隐式类型转换

2、函数模板会自动类型推导,不可以发生隐式类型转换

3、函数模板用显示指定类型,可以发生隐式类型转换

1.2.4普通函数与函数模板调用规则

1、如果函数模板和普通函数都可以实现,优先调用普通函数

2、可以通过空模板参数列表来强调调用函数模板

3、函数模板也可以发生重载

4、如果函数模板可以产生更好的匹配,优先调用函数模板

1.3类模板
1.3.1类模板语法

作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表

语法:

template<typename T>

template:声明创建模板

typename:表明其后面的符合是一种数据类型,可以用class代替

T:通用的数据类型,名称可以替换,通常为大写字母

template<class NameType,class AgeType> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name:" << this->m_Name << "age:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; void test() { Person<string, int>p1("孙悟空", 999); p1.showPerson(); }

1.3.2类模板与函数模板区别

1、类模板没有自动类型推导的使用方式

2、类模板在模板参数列表中可以有 默认参数

1.3.3类模板中成员函数创建时机

普通类中的成员函数一开始就可以创建

类模板中的成员函数在调用时才创建

1.3.4类模板对象做函数参数

三种传入方式:

1、指定传入的类型:直接显示对象的数据类型

2、参数模板化:将对象中的参数变为模板进行传递

3、整个类模板化:将这个对象类型模板化进行传递

#include<iostream> #include<string> using namespace std;

template<class NameType,class AgeType> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name:" << this->m_Name << "age:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; //1、指定传入的类型 void printPerson1(Person<string, int>& p) { p.showPerson(); } void test01() { Person<string, int>p1("孙悟空", 999); printPerson1(p1); } //2、参数模板化 template<class NameType, class AgeType> void printPerson2(Person<NameType, AgeType>& p) { p.showPerson(); cout << "NameType的类型为:" << typeid(NameType).name() << endl; cout << "AgeType的类型为:" << typeid(AgeType).name() << endl; } void test02() { Person<string, int>p2("猪八戒", 99); printPerson2(p2); } //3、整个类模板化 template<class T> void printPerson3(T &p) { p.showPerson(); } void test03() { Person<string, int>p3("唐僧", 30); printPerson3(p3); } int main() { test01(); test02(); test03(); }

1.3.4类模板与继承

当子类继承的父类是一个类模板时,子类在声明的时候,要指出父类中T的类型

如果不指定,编译器无法给子类分配内存

如果想灵活指出父类中T的类型,子类也需变为类模板

1.3.5类模板成员函数类外实现

//构造函数类外实现 template<class NameType, class AgeType> Person<NameType, AgeType>::Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; }

//成员函数的类外实现 template<class NameType, class AgeType> void Person<class NameType, class AgeType>::showPerson() { cout << "name:" << this->m_Name << "age:" << this->m_Age << endl; }

类模板中成员函数类外实现时,需要加上模板参数列表

1.3.6类模板分文件编写

问题:类模板中的成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

解决方式1:直接包含.cpp源文件

解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,.hpp是约定的名称,并不是强制

1.3.7类模板与友元

全局函数类内实现:直接在类内声明友元即可

全局函数类外实现:需要提前让编译器知道全局函数的存在

//全局函数 类内实现

friend void printPerson(Person<class NameType, class AgeType> p) { cout << "name:" << p.m_Name << "age:" << p.m_Age << endl; }

1.3.8类模板例题-通用数组类

要求:

可以对内置数据类型以及自定义数据类型的数据进行存储

将数组中的数据存储到堆区

构造函数中可以传入数组的容量

提供对应的拷贝构造函数以及operator=防止浅拷贝问题

提供尾插法和尾删法对数组中的数据进行增加和删除

可以通过下标的方式访问数组中的元素

可以获取数组中当前元素个数和数组的容量

#pragma once #include<iostream> using namespace std;

template<class T> class MyArray { public: //有参构造 参数:容量 MyArray(int capacity) { //cout << "调用有参构造函数" << endl; this->m_Capacity = capacity; this->m_Size = 0; this->pAddress = new T[this->m_Capacity]; }

//尾插法
void Push_Back(const T & val) {
	//判断容量是否等于大小
	if (this->m_Capacity == this->m_Size) {
		return;
	}
	this->pAddress[this->m_Size] = val;//在数组末尾插入数据
	this->m_Size++;
}

//尾删法
void Pop_Back() {
	//让用户访问不到最后一个元素
	if (this->m_Size == 0) {
		return;
	}
	this->m_Size--;
}

//通过下标方式访问数组中的元素
T& operator[](int index) {
	return this->pAddress[index];
}

//返回数组容量
int getCapacity() {
	return this->m_Capacity;
}
//返回数组大小
int getSize() {
	return this->m_Size;
}

//析构函数
~MyArray() {
	//cout << "调用析构构造函数" << endl;
	if (this->pAddress != NULL) {
		delete[] this->pAddress;
		this->pAddress = NULL;
	}
}
//拷贝构造
MyArray(const MyArray& arr) {
	//cout << "调用拷贝构造函数" << endl;
	this->m_Capacity = arr.m_Capacity;
	this->m_Size = arr.m_Size;

	//深拷贝
	this->pAddress = new T[arr.m_Capacity];
	//将arr中的数据都拷贝过来
	for (int i = 0;i < this->m_Size;i++) {
		this->pAddress[i] = arr.pAddress[i];
	}
}

MyArray& operator=(const MyArray &arr) {
	//cout << "调用operator构造函数" << endl;
	//先判断原来堆区是否有数据,如果有先释放
	if (this->pAddress != NULL) {
		delete[] this->pAddress;
		this->pAddress = NULL;
		this->m_Capacity = 0;
		this->m_Size = 0;
	}
	//深拷贝
	this->m_Capacity = arr.m_Capacity;
	this->m_Size = arr.m_Size;
	this->pAddress = new T[this->m_Capacity];
	for (int i = 0;i < this->m_Size;i++) {
		this->pAddress[i] = arr.pAddress[i];
	}
	return *this;
}

private: T* pAddress;//数组 int m_Capacity;//数组容量 int m_Size;//数组大小 };

二、STL

2.1 STL基本概念

STL(Standard Template Library,表中模板库)

STL从广义上分为:容器 算法 迭代器

容器和算法之间通过迭代器进行无缝连接

STL激活所有的代码都采用了模板类或者模板函数

2.2 STL六大组件

1、容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据

2、算法:各种常用的算法,如:sort、find、copy、for_each等

3、迭代器:扮演了容器与算法之间的胶合剂

4、仿函数:行为类似函数,可作为算法的某种策略

5、适配器:一种用来修饰容器或者仿函数或迭代器接口的东西

6、空间配置器:负责空间的配置与管理

2.3 STL中容器、算法、迭代器
2.3.1容器

STL容器就是将运用最广泛的一些数据结构实现出来

常用的数据结构:数组、链表、树、栈、队列、集合、映射表等

这些容器分为序列式容器和关联式容器两种:

序列式容器:强调值的排序,序列式容器中每个元素均有固定的位置

关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

2.3.2 算法

有限的步骤,解决逻辑或数学上的问题,这一门学科叫做算法

算法分为:质变算法和非质变算法

质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝、替换、删除等

非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等

2.3.3 迭代器

提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式

每个容器都有自己专属的迭代器

迭代器使用非常类似于指针

双向迭代器:读写操作,并能向前和向后操作

随机访问迭代器:读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器

2.4 容器算法迭代器初识
2.4.1 vector存放内置数据类型

容器:vector

算法:for_each

迭代器:vector<int>::iterator

#include<iostream> #include<string> #include<vector> #include<algorithm>//标准算法头文件 using namespace std;

void myPrint(int val) { cout << val << endl; } //vector容器存放内置数据类型 void test() { //创建了一个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 != itEnd) {
	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() { test(); }

2.4.2 vector存放自定义数据类型

class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; void test() { vector<Person>v; Person p1("aa1", 10); Person p2("aa2", 20); Person p3("aa3", 30); Person p4("aa4", 40); Person p5("aa5", 50);

//向容器中添加数据
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
//遍历容器中的数据
for (vector<Person>::iterator it = v.begin();it != v.end();it++) {
	//cout << "name:" << (*it).m_Name << "age:" << (*it).m_Age << endl;
	cout << "name:" << it->m_Name << "age:" << it->m_Age << endl;
}

}

三、STL-常用容器

3.1string容器

string是c++风格的字符串,而string本质上是一个类

string和char*的区别:

char*是一个指针

string是一个类,类内部封装了char,管理这个字符串,是一个char型的容器

string类内部封装了很多成员方法

string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责

3.1.1string构造函数

string(); //创建一个空的字符串,例如:string str;

string(const char* s); //使用字符串s初始化

string(const string& str); //使用一个string对象初始化另一个string对象

string(int n,char c); //使用n个字符c初始化

void test() { string str1;

const char* str = "hello world";
string str2(str);
cout << "str2= " << str2 << endl;

string str3(str2);
cout << "str3= " << str3 << endl;

string str4(10, 'a');
cout << "str4= " << str4 << endl;

}

3.1.2string赋值操作

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的前几个字符赋给当前的字符串

string& assign=(const string &s); //把字符串s赋给当前的字符串

string& assign=(int n,char c); //用n个字符c赋值给当前字符串

3.1.3string字符串拼接

string& operator+=(const char* str); //重载+=操作符

string& operator+=(const char c); //重载+=操作符

string& operator+=(const string& str); //重载+=操作符

string& append(const char *s); //把字符串s连接到当前字符串结尾

string& append(const char *s,int n); //把字符串s的前n个字符连接到当前字符串结尾

string append(const string &s); //同operator+=(const string& str)

string& append(const string &s,int pos,int n); //字符串s中从pos开始的n个字符连接到字符串结尾

3.1.4string查找和替换

int find(const string& str,int pos=0) const; //查找str第一次出现位置,从pos开始查找

int find(const char *s,int pos=0) const; //查找s第一次出现位置,从pos开始查找

inr find(const char*s ,int pos,int n)const; //从pos位置查找s的前n个字符第一次位置

int find(const char c,int pos=0)const; //查找字符c第一次出现位置

int rfind(const string& str,int pos=npos) const; //查找str最后一次位置,从pos开始查找

int rfind(const char* s,int pos=npos) const; //查找s最后一次出现位置,从pos开始查找

int rfind(const char* s,int pos,int n) const; //从pos查找s的前n个字符最后一次位置

int rfind(const char c,int pos=0) const; //查找字符c最后一次出现位置

string& replace(int pos,int n,const string& str); //替换从pos开始n个字符为字符串str

string& replace(int pos,int n,const char* s); //替换从pos开始的n个字符为字符串s

3.1.5string字符串比较

字符串之间的比较

int compare(const string &s)const; //与字符串s比较

int compare(const char *s)const; //与字符串s比较

3.1.6string字符存取

string中单个字符存取方式有两种

char& operator; //通过[]方式取字符

char& at(int n); //通过at方法获取字符

3.1.7string插入和删除

对string字符串进行插入和删除字符操作

string& insert(int pos,const char* s); //插入字符串

string& insert(int pos,const string& str); //插入字符串

string& insert(int pos,int n,char c); //在指定位置插入n个字符c

string& erase(int pos,int n=npos); //删除从pos开始的第n字符

3.1.8string子串

从字符串中获取想要的子串

string substr(int pos=0,int n=npos) const; //返回由pos开始的n个字符组成的字符串

3.2vector容器

verctor数据结构和数组非常相似,也称为单端数组

vector与普通数组区别:不同之处在于数组是静态空间,而vector可以动态扩展

动态扩展并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间

vector容器的迭代器是支持随机访问的迭代器

3.2.1vector构造函数

创建vector容器

vector<T> v; //采用模板实现类实现,默认构造函数

vector<v.begin(),v.end()); //将v[begin(),end()]区间中的元素拷贝给本身

vector(n,elem); //构造函数将n个elem拷贝给本身

vector(const vector &vec); //拷贝构造函数

3.2.2vector赋值操作

给vector容器进行赋值

vector& operator=(const vector &vec); //重载等号操作符

assign(beg,end); //将[beg,end]区间中的数据拷贝赋值给本身

assign(n,elem); //将n个elem拷贝赋值给本身

3.2.3vector容量和大小

对vector容器的容量和大小操作

empty(); //判断容器是否为空

capacity(); //容器的容量

size(); //返回容器中元素的个数

resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除

resize(int num,elem); //重新指定容器的长度为num,若容器变长,则以elem填充新位置,如果容器变短,则末尾超出容器长度的元素被删除

3.2.4vector插入和删除

对vector容器进行插入、删除操作

push_back(ele); //尾部插入元素ele

pop_back(); //删除最后一个元素

insert(const_iterator pos,ele); //迭代器指向位置pos插入元素ele

insert(const_iterator pos,int count,ele); //迭代器指向位置插入count个元素ele

insert(const_iterator pos); //删除迭代器指向的元素

erase(const_iterator start,const_iterator end); //删除迭代器从start到end之间的元素

clear(); //删除容器中所有元素

3.2.5vector数据存取

对vector中的数据的存取操作

at(int idx); //返回索引idx所指的数据

operator[ ]; //返回索引idx所指的数据

front(); //返回容器中第一个数据元素

back(); //返回容器中最后一个数据元素

3.2.6vector互换容器

实现两个容器内元素进行互换

swap(vec); //将vec与本身的元素互换

swap可以使得两个容器互换,可以达到实用的收缩内存效果

3.2.7vector预留空间

减少vector在动态扩展容量时的扩展次数

reserve(int len); //容器预留len个元素长度,预留位置不初始化,元素不可访问

3.3deque容器

功能:双端数组,可以对头端进行插入删除操作

deque和vector区别:

vector对于头部的插入删除效率低,数据量越大,效率越低

deque相对而言。对太头部的插入删除速度会比vector快

vector访问元素的速度会比deque快

deque内部工作原理:

deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据

中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间

deque容器的迭代器也是支持随机访问的

3.3.1deque构造函数

deque<T> deq T; 默认构造形式

deque(beg, end); //构造函数将[beg, end)区间中的元素拷贝给本身

deque(n,elem); //构造函数将n个elem拷贝给本身

deque(const deque &deq); //拷贝构造函数

3.3.2deque赋值操作

deque& operator=(const deque &deq); //重载等号操作符

assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身

assign(n, elem); //将n个elem拷贝赋值给本身

3.3.3deque大小操作

deque.empty(); //判断容器是否为空

deque.size(); //返回容器中元素的个数

deque.resize(num); //重新指定容器长度为num,若容器变长,则以默认值填充新位置,

//如果容器变短,则末尾超出长度的元素被删除

deque.resize(num,elem); //重新指定容器长度为num,若容器变长,则以elem填充新位置,

//如果容器变短,则末尾超出长度的元素被删除

3.3.4deque插入和删除

两端插入操作:

push_back(elem); //在容器尾部添加一个数据

push_front(elem); //在容器头部插入一个数据

pop_back( ); //删除容器最后一个数据

pop_front( ); //删除容器第一个数据

指定位置操作:

insert(pos, elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置

insert(pos, n, elem); //在pos位置插入n个elem数据,无返回值

insert(pos, beg, end); //在pos位置插入[beg,end)区间的数据,无返回值

clear(); //清空容器的所有数据

erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置

erase(pos); //删除pos位置的数据,返回下一个数据的位置

3.3.5deque数据存取

at(int idx); //返回索引idx所指的数据

operator[]; //返回索引idx所指的数据

front(); //返回容器中的第一个数据元素

back(); //返回容器中最后一个数据元素

3.3.6deque排序

sort(iterator beg, iterator end); //对beg和end区间内元素进行排序

3.4案例

#include<iostream> #include<string> #include<vector> #include<deque> #include<algorithm> using namespace std; //有五名选手:A,B,C,D,E,10个评委分别对每一名选手打分,去除最高分和最低分,取平均分 //选手类 class Person { public: Person(string name, int score) { this->m_Name = name; this->m_Score = score; } string m_Name; int m_Score; }; void createPerson(vector<Person>& v) { string nameSeed = "ABCDE"; for (int i = 0;i < 5;i++) { string name = "选手"; name += nameSeed[i]; int score = 0; Person p(name, score); //将创建的person对象放到容器中 v.push_back(p); } } //打分 void setScore(vector<Person>& v) { for (vector<Person>::iterator it = v.begin();it != v.end();it++) { //将评委的分数放入到deque容器中 deque<int>d; for (int i = 0;i < 10;i++) { int score = rand() % 41 + 60;//60~100 d.push_back(score); } //排序 sort(d.begin(), d.end()); //去除最高分和最低分 d.pop_back(); d.pop_front(); //取平均分 int sum = 0; for (deque<int>::iterator dit = d.begin();dit != d.end();dit++) { sum += *dit;//累加每个评委的分数 } int avg = sum / d.size(); //将平均分赋值给选手身上 it->m_Score = avg;

}

} int main() { //创建5名选手 vector<Person> v; createPerson(v);

//给5名选手打分
setScore(v);
//显示最后得分

}

3.5stack容器

stack是一种先进先出的数据结构,它只有一个出口

栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为

栈容器常用的对外接口

构造函数:

stack<T> stk; //stack采用模板类实现,stack对象的默认构造形式

stack(const stack &stk); //拷贝构造函数

赋值操作:

stack& operator=(const stack &stk); // 重载等号操作符

数据存取:

push(elem); //向栈顶添加元素

pop(); //从栈顶移除第一个元素

top(); //返回栈顶元素

大小操作:

empty();

size();

3.6queue容器

queue是一种先进先出的数据结构,有两个出口

队列容器允许从一端新增元素,从另一端移除元素

队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为

构造函数:

queue<T> que; //queue采用模板类实现,queue对象的默认构造形式

queue(const queue&que); //拷贝构造函数

赋值操作:

queue& operator=(const queue&que); // 重载等号操作符

数据存取:

push(elem); //向队尾添加元素

pop(); //从队头移除第一个元素

back(); //返回最后一个元素

front(); // 返回第一个元素

大小操作:

empty(); //判断堆栈是否为空

size(); //返回第一个元素

3.7list容器

功能:将数据进行链式存储

STL中的链表是一个双向循环链表

由于链表的存储方式不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list一个重要性质:插入操作和删除操作都不会造成原有list迭代器的失效

3.7.1list构造函数

创建list容器

list<T> lst; //list采用模板类实现,对象的默认构造形式

list(beg,end); //构造函数将[beg,end]区间中的元素拷贝给本身

list(n,elem); //构造函数将n个elem拷贝给本身

list(const list &lst); //拷贝构造

3.7.2list赋值和交换

功能:给list容器进行赋值,以及交换list容器

assign(beg,end); //将[beg,end]区间中的数据拷贝赋值给本身

assign(n,elem); //将n个elem拷贝赋值给本身

list& operator=(const list &lst); //重载等号操作符

swap(lst); //将lst与本身的元素互换

3.7.3list大小操作

对list容器的大小进行操作

size(); //返回容器中元素的个数

empty(); //判断容器是否为空

resize(); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置

//如果容器变短,则末尾超出容器长度的元素被删除

resize(num,elem); //重新指定容器的长度为num,若容器变长,则以elem填充新位置

//如果容器变短,则末尾超出容器长度的元素被删除

3.7.4list插入和删除

功能:对list容器进行数据的插入和删除

push_back(elem); //在容器尾部加入一个元素

pop_back(); //删除容器中最后一个元素

push_front(elem); //在容器开头插入一个元素

pop_front(); //在容器开头移除第一个元素

insert(pos,elem); //在pos位置插入elem元素的拷贝,返回新数据的位置

insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值

insert(pos,beg,end); //在pos位置插入[beg,end]区间的数据,无返回值

clear(); //移除容器的所有数据

erase(beg,end); //删除[beg,end]区间的数据,返回下一个数据的位置

erase(pos); //删除pos位置的数据,返回下一个数据的位置

remove(elem); //删除容器中所有与elem值匹配的元素

3.7.5list数据存取

功能:对list容器中数据进行存取

front(); //返回第一个元素

back(); //返回最后一个元素

3.7.6list反转和排序

功能:将容器中的元素反转,以及将容器中的数据进行排序

reverse(); //反转链表

sort(); //链表排序

3.8set/multiset容器

所有元素都会在插入时自动被排序

set/multiset属于关联式容器,底层结构是用二叉树实现

set和multiset区别:

set不允许容器中有重复的元素

multiset允许容器中有重复的元素

3.8.1set构造和赋值

创建set容器以及赋值

构造:

set<T> st; //默认构造函数

set(const set &st); //默认拷贝构造函数

赋值:

set& operator=(const set &st); //重载等号操作符

3.8.2set大小和交换

size(); //返回容器中元素的数目

empty(); //判断容器是否为空

swap(st); //交换两个集合容器

3.8.3set插入和删除

insert(elem); //在容器中插入元素

clear(); //清除所有元素

erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器

erase(beg, end); //删除区间[beg, end]的所有元素,返回下一个元素的迭代器

erase(elem); //删除容器中值为elem的元素

3.8.4set查找和统计

对set容器进行查找数据以及统计数据

find(key); //查找key是否存在,若存在,返回该键的元素的迭代器,若不存在,返回set.end()

count(key); //统计key的元素个数

3.8.5set和multiset区别

set不可以插入重复数据而multiset可以

set插入数据的时候同时会返回插入结果,表示插入是否成功

multiset不会检测数据,因此可以插入重复数据

3.8.6pair对组创建

成对出现的数据,利用对组可以返回两个数据

两种创建方式:

pair<type, type> p (value1, value2);

pair<type, type> p = make_pair(value1, value2);

3.8.7set容器排序

set容器默认排序规则为从小到大

利用仿函数,可以改变排序规则

#include<set>

class MyCompare{

public:

bool operator()(int v1,int v2){

return v1>v2;

}

}

void test(){

set<int> s1;

s1.insert(10);

s1.insert(20);

s1.insert(30);

s1.insert(40);

s1.insert(50);

//默认从小到大

for(set<int>::iterator it=s1.begin(); it!=s1.end(); it++){

cout<<*it<<" ";

}

cout<<endl;

}

//指定排序规则

for(set<int MyCompare>::iterator it = s2.beign(); it!=s2.end(); it++){

cout<<*it<<" ";

}

cout<<endl;

}

int main(){

test();

}

3.9map/multimap容器

map中所有元素都是pair

pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)

所有元素都会根据元素的键值自动排序

可以根据key值快速找到value值

map和multimap的区别:

map不允许容器中有重复key值元素

multimap允许容器中有重复key值元素

3.9.1map构造和赋值

构造:

map<T1,T2> mp; //map默认构造函数

map(const map &mp); //拷贝构造函数

赋值:

map& operator=(const map &mp); //重载等号操作符

3.9.2map大小和交换

size(); //返回容器中元素的数目

empty(); //判断容器是否为空

swap(st); //交换两个集合容器

3.9.3map插入和删除

insert(elem); //在容器中插入元素

clear(); //清除所有元素

erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器

erase(beg, end); //删除区间[beg,end]的所有元素,返回下一个元素的迭代器

erase(key); //删除容器中值为key的元素

3.9.4map查找和统计

find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end()

count(key); //统计key的元素个数

3.9.5map容器排序

默认从小到大排序

可以利用仿函数实现从大到小排序

四、STL-函数对象

4.1函数对象概念

重载函数调用操作符的类,其对象常称为函数对象

函数对象使用重载的()时,行为类似函数调用,也叫仿函数

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

特点:

函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值

函数对象超出普通函数的概念,函数对象可以有自己的状态

函数对象可以作为参数传递

4.2谓词

返回bool类型的仿函数称为谓词

如果operator()接受一个参数,那么叫做一元谓词

如果operator()接受两个参数,那么叫做二元谓词

4.3内建函数对象

算术仿函数、关系仿函数、逻辑仿函数

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

4.3.1算术仿函数

实现四则运算

template<class T> T plus<T> // 加法仿函数

template<class T> T minus<T> // 减法仿函数

template<class T> T multiplies<T> // 乘法仿函数

template<class T> T divides<T> // 除法仿函数

template<class T> T modulus<T> // 取模仿函数

template<class T> T negate<T> // 取反仿函数

4.3.2关系仿函数

实现关系对比

template<class T> bool equal_to<T> // 等于

template<class T> bool not_equal<T> // 不等于

template<class T> bool greater<T> // 大于

template<class T> bool greater_equal<T> // 大于等于

template<class T> bool less<T> // 小于

template<class T> bool less_equal<T> // 小于等于

4.3.3逻辑仿函数

实现逻辑运算

template<class T> bool logical_and<T> // 逻辑与

template<class T> bool logical_or<T> // 逻辑或

template<class T> bool logical_not<T> // 逻辑非

五、STL-常用算法

5.1常用遍历算法

for_each //遍历容器

transform //搬运容器到另一个容器中

5.1.1for_each

实现遍历容器

for_each(iterator beg,iterator end, _func);

//beg开始迭代器,end结束迭代器,_func函数或者函数对象

5.1.2transform

transform(iterator beg1,iterator end1,iterator beg2, _func);

//beg1源容器开始迭代器

//end1源容器结束迭代器

//beg2目标容器开始迭代器

//_func函数或者函数对象

搬运的目标容器必须要提前开辟空间,否则无法正常搬运

5.2常用查找算法

find //查找元素

find_if //按条件查找元素

adjacent_find //查找相邻重复元素

binary_search //二分查找法

count //统计元素个数

count_if //按条件统计元素个数

5.2.1 find

查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end()

find(iterator beg, iterator end, value);

//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg开始迭代器

//end结束迭代器

//value查找的元素

#include<iostream>

#include<string>

#include<vector>

#include<deque>

#include<algorithm>

using namespace std;

class Person

{

public:

Person(string name, int age) { this->m_Name = name; this->m_Age = age; } //重载 == 底层find知道如何对比person数据类型 bool operator==(const Person &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } else { return false; } } string m_Name; int m_Age; };

void test() { vector<Person>v; Person p1("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); vector<Person>::iterator it = find(v.begin(), v.end(), p2); if (it == v.end()) { cout << "没有找到" << endl; } else { cout << "找到姓名:" << it->m_Name << "年龄:" << it->m_Age << endl; } }

int main() { test(); }

5.2.2find_if

按条件查找元素

find_if(iterator beg, iterator end,_Pred);

//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg开始迭代器

//end结束迭代器

//_Pred函数或者谓词(返回bool类型的仿函数)

#include<iostream> #include<string> #include<vector> #include<deque> #include<algorithm> using namespace std;

//1、查找内置数据类型 class GreaterFive { public: bool operator()(int val) { return val > 5; } }; void test1() { vector<int>v; for (int i = 0;i < 10;i++) { v.push_back(i); } vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive()); if (it == v.end()) { cout << "未找到" << endl; } else { cout << "找到:" << *it << endl; } }

//2、查找自定义数据类型

class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; class Greater20 { public: bool operator()(Person& p) { return p.m_Age > 20; } }; void test2() { vector<Person>v; Person p1("aaa", 10); Person p2("bbb", 20); Person p3("ccc", 30); Person p4("ddd", 40); v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); vector<Person> ::iterator it = find_if(v.begin(), v.end(), Greater20()); if (it == v.end()) { cout << "未找到" << endl; } else { cout << "找到年龄大于20岁的 姓名:" << it->m_Name << "年龄:" << it->m_Age << endl; } } int main() { test1(); test2(); }

5.2.3 adjacent_find

查找相邻重复元素

adjacent_find(iterator beg, iterator end);

//查找相邻重复元素,返回相邻元素的第一个位置的迭代器

//beg开始迭代器

//end结束迭代器

vector<int>::iterator pos=adjacent_find(v.begin(), v.end());

5.2.4 binary_search

二分查找, 查找指定元素是否存在

bool binary_search(iterator beg, iterator end, value);

//查找指定的元素,查到返回true,否则false

//注意:在无序序列中不可用

//beg开始迭代器

//end结束迭代器

//value查找的元素

bool ret = binary_search(v.begin(), v.end(), 9);

5.2.5 count

统计元素个数

count(iterator beg, iterator end, value);

//统计元素出现的次数

//beg开始迭代器

//end结束迭代器

//value统计的元素

5.2.6count_if

按条件统计元素个数

count_if(iterator beg, iterator end, _Pred);

//按条件统计元素出现次数

//beg开始迭代器

//end结束迭代器

//_Pred谓词

5.3 常用排序算法

sort //对容器内元素进行排序

random_shuffle //洗牌,指定范围内的元素随机调整次序

merge //容器元素合并,并存储到另一个容器中

reverse //反转指定范围的元素

5.3.1 sort

对容器内元素进行排序

sort(iterator beg, iterator end, _Pred);

//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg开始迭代器

//end结束迭代器

//_Pred谓词

5.3.2 random_shuffle

洗牌,指定范围内的元素随机调整次序

random_shuffle(iterator beg, iterator end);

//指定范围内的元素随机调整次序

//beg开始迭代器

//end结束迭代器

5.3.3 merge

容器元素合并,并存储到另一个容器中

merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

//容器元素合并,并存储到另一容器中

//注意:两个容器必须是有序的

//beg1 容器1开始迭代器

//end1 容器1结束迭代器

//beg2 容器2开始迭代器

//end2 容器2结束迭代器

//dest 目标容器开始迭代器

5.3.4 reverse

将容器内元素进行反转

reverse(iterator beg, iterator end);

//反转指定范围的元素

//beg开始迭代器

//end结束迭代器

5.4常用拷贝和替换算法

copy //容器内指定范围的元素拷贝到另一容器中

replace //将容器内指定范围的旧元素修改为新元素

replace_if //容器内指定范围满足条件的元素替换为新元素

swap //互换两个容器的元素

5.4.1 copy

容器内指定范围的元素拷贝到另一容器中

copy(iterator beg, iterator end, iterator dest);

//按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

//beg开始迭代器

//end结束迭代器

//dest目标起始迭代器

5.4.2 replace

将容器内指定范围的旧元素修改为新元素

replace(iterator beg, iterator end, oldvalue, newvalue);

//将区间内旧元素替换成新元素

//beg开始迭代器

//end结束迭代器

//oldvalue旧元素

//newvalue新元素

5.4.3 replace_if

将区间内满足条件的元素,替换成指定元素

replace_if(iterator beg, iterator end, _pred, newvalue);

//按条件替换元素,满足条件的替换成指定元素

//beg开始迭代器

//end结束迭代器

//_pred谓词

//newvalue替换的新元素

5.4.4 swap

互换两个容器的元素

swap(container c1, container c2);

//互换两个容器的元素

//c1容器1

//c2容器2

5.5 常用算术生成算法

算术生成算法属于小型算法,使用时包含的头文件为#include<numeric>

accumulate //计算容器元素累计总和

fill //向容器中添加元素

5.5.1 accumulate

计算区间内容器元素累计总和

accumulate(iterator beg, iterator end, value);

//计算容器元素累计总和

//beg开始迭代器

//end结束迭代器

//value起始值

5.5.2 fill

向容器中添加元素

fill(iterator beg, iterator end, value);

//向容器中填充元素

//beg开始迭代器

//end结束迭代器

//value起始值

5.6常用集合算法

set_intersection //求两个容器的交集

set_union //求两个容器的并集

set_difference //求两个容器的差集

5.6.1set_intersection

求两个容器的交集

set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

//求两个集合的交集

//注意:两个集合必须是有序序列

5.6.2 set_union

求两个容器的并集

set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

//求两个集合的并集

//注意:两个集合必须是有序序列

5.6.3set_difference

求两个容器的差集

set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

//求两个集合的差集

//注意:两个集合必须是有序序列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值