C++笔记

C++笔记

1.C++前序

1.1参考书籍

《c++ primer》、《c++ standard Library》、《inside c++ object model》

1.2c++的编程范式(不同的编程风格)

1、结构化编程,函数+数据,数据暴露在外,如果被一个函数修改,另一个函数可能不知道
2、面向对象编程,封装,开发维护容易,数据类型变了,程序员得重写函数
3、泛型编程,数据类型变了,编译器自动处理,模板
4、函数式编程,不受外界的额外影响,还可以接受其他函数作为输入、输出

1.3‘\n’和endl的区别

‘\n’只输出换行,缓冲区满了自动刷新,cout<<endl表示输出换行并且刷新缓冲区

1.4更改__cplusplus

在工程属性内更改c++标准后,在命令号的其他选项内添加:/Zc::__cplusplus
设置启动项:在解决方案的属性内,启动项目中选择当前选中项目

1.5msys2安装软件

pacman -s <软件名称>
ubuntu下安装:sudo apt-get install <软件名称>

1.6 .h和.cpp文件

C++中:头文件.h或者.hpp,源文件:.cpp或.cxx
printf();函数在stdio.h里面

1.7输入输出流

cin >>流提取运算符
cin.get()从流中提取一个字符
cin.getline()从流中提取字符,直至行尾或指定的分隔符
cin.ignore()从流中读取并舍弃指定数量的字符
cout <<流插入运算符
cout.put()将字符写到流中(无格式)
cout.flush()将流中缓冲内容全部输出

1.8引用和指针

引用就是这个变量的另一个名字,例如
int a;
引用的两种种方式(别名):
int& r{a};int a,&r=a;
int& r=a;
指针指向地址:
int r=&a;
&表示取地址,表示取地址的值,操作引用就是操作原变量,一旦引用,引用的名字就不能引用其他的
通过指针传参swap(int
x,int
y)swap(&x,&y)、通过引用传参swap(int& x,int& y)swap(x,y)改值,通过值传参swap(int x,int y)swap(x,y)不改变值
a++先返回再运算,++a先运算再返回

1.9const常量和指针

extern const int a;全局常量,在其他.cpp文件也可以使用
const的作用,防止变量的值被直接或间接修改
const常量和宏常量的区别:const常量有类型,可以进行安全检查,宏常量没有数据类型,不能对其进行安全检查,知识对字符串进行替换
const的作用:类型检查、保护数据、方便修改、为函数重载提供一个参考、常量保存在符号表中,节省空间和提升效率。
const int p;//p指向的数值不发生变化,指的地址可以变化
int
const p;//p指向的数值可以变化,指的地址不发生变化
cosnt int* const p//两者都不能发生变化
技巧p是数值,p是指向,const修饰的是什么什么就不能发生变化
const函数能访问const成员,其他函数不能。const对象只能调用const函数
const char
s;
const char*& r=s;
对r的操作就是对s的操作
引用是起别名,实际上在编译过程是通过指针实现的,需要定义指针变量,占用内存空间
&运算符的三个含义:位与、取地址、定义一个引用类型

1.10空指针和动态申请内存

申请动态内存
new <类型名>(初值)//申请一个变量的空间
int* s1=new int(10);
new <类型名>[常量表达式]//申请数组
int* s1=new int[10];
auto c5=new Circle[3]{1.0,2.0,3.0};
delete [] c5;
c5=nullptr;
C++中,NULL和nullptr的区别
NULL是宏定义,即#define NULL 0
nullptr是空指针void
如果申请成功,返回指定类型的值
如果申请失败,抛出异常,或者返回空指针(nullptr)
释放内存
delete <指针名>;//删除一个变量/对象
delete s1;
delete[] <指针名>;//删除数组
delete[] s1;
空指针的地址是8个0,申请地址后发生变化
删除变量后地址是00008123,地址发生变化
在C语言中用的是malloc()和free(),申请和释放内存

1.11 C/C++在申请动态内存上的异同

1.alloc/free和new/delete都可以动态申请内存
2.malloc/free是库函数,不能执行构造函数和析构函数;new/delete是运算符,在申请的时候可以执行构造函数,在释放的时候可以执行析构函数
3.malloc开辟的空间需要自己计算,new是编译器计算
4.malloc返回的是void需要强制转换为对应的指针类型,new直接返回的指针
5.malloc在堆上分配内存;new既可以在堆上,也可以在静态储存区分配,取决于operateor new实现细节,取决于他在哪里为对象分配空间

cout<<boolalpha<<isAlpha<<endl;
boolalpha的作用是告诉cout输出时输出false或者true
换行时要在逗号或者运算符换行,新行要对齐

1.12 列表初始化:直接列表初始化、拷贝列表初始化

//direct list initialization
int a{1};//会检查,如果是1.0的话就会警告
int array[]{1,2,3};
char s1[3]{‘o’,’k’};
char s3[]{‘hello’}
//copy list initialization
int a={1};
int array2={4,5,6}
char s2={‘y’,’e’,’s’};
char s4={“world”};
char s5=’aloha’
尽量使用列表初始化,列表初始化不允许窄化,就是不允许丢失数据精度的隐式类型转换
窄化:变量类型的范围和精度低于列表内的值
int x=1.1;//不会报错
int x{1.1};//编译器检查,会提示导致数据精度丢失
initializer_list:未见解决传递参数不唯一,创建一个临时对象,将参数传递给函数

1.13 类型转换

C:int(2.5)、(int)2.5
C++:static_cast(1)
cosnt_cast<int*>(a)//去除指针变量的常量属性
reinterpret_cast<double*>(a)//编译器的解释方法发生变化,但是指向的地址不变

类型系统:将type属性指定给不同的计算机程序构造块
C++:是静态类型语言,脚本语言是动态类型

1.14 auto和dcltype

auto:为变量的初始值自动类型推导
1.auto变量必须在定义时初始化,类似于const类型
2.定义在同一个auto序列的变量必须是推导成同一类型
3.如果初始化表达式是引用或者const,去除引用或const
4.如果auto关键字加&号,不去除引用或者const
5.初始化为数组是,auto关键字的推导类型是指针
6.若表达式为数组且auto后带&,则推导类型为数组类型
7.auto可以作为函数的返回值类型和参数类型
typeid(a).name()//判断a的类型
auto的使用
1.希望确定其类型,auto x=int{3}
2.避免在同一行使用直接列表初始化和拷贝列表初始化,例如auto x{1}.y={2};//这样有问题
3.现代c++
auto x=42;//int x=42;
auto x=42.f;//float x=42;
auto x=42ul;//unsigned long x=42;
auto x=”42”s;//std::string x=”42”;
auto x=42ns;//chrono::nonseconds x{42};
auto f(double)->int;
auto f(double){//};
auto f=[]double{//};//int f(double)
auto不能用于声明数组

自动推导类型decltype:利用已知的类型声明新变量,在编译的时候执行操作
decltype用于泛型编程(模板)

1.15 内存模型

  1. Stack栈
    编译器自动分配内存
  2. Heap堆
    由程序员分配释放,若程序员不释放,程序结束时可能有OS释放
    3.Global/Static全局区/静态区
    全局变量和静态变量放在一起的
    程序启动全局/静态变量就在此处,程序结束释放
    4.Constant常量区
    所有常量放在一起,内容不可修改

1.16 常量和指针

指针的属性:指针变量本身、指针变量所指向的数据
数组名是一个指针常量
const int *b;
*(指针)和const(常量),谁在前先读谁
*代表被指的数据,名字代表指针指向的地址
const在谁前,谁就不能变
常量初始化要赋值

1.17 #define、typedef、using

#define是一种预处理指示符,定义一个宏,编译器不做类型检查
#define MACRONAME Something,结尾无分号
typedef创建一个类型名的别名
typedef SomeType NewTypeName
Type _Bool bool;//C99没有bool关键字,所以在<stdio.h>中这样定义

用using替代typedef(C++11),只能用于类型,代表类型的名字必须首字母大写,并且其他字母大小写混合
using identifier=type-id
using Uint=unsigned int;
//下面,(*)表示函数,这是一个指向函数的指针
//等同于 typedef void (FuncType)(int,int);,名称FuncType现指代指向函数的指针类型
using FuncType=void (
)(int,int)
void example(int,int){};
FuncType f=example;
定义模板的时候只能使用using,不能使用其他
常函数:void fun() const{}
构造函数和析构函数不能是常函数
特点:①:可以使用数据成员,但是不能修改;②:常对象只能调用常函数,不能调用普通函数;③:常函数的this指针就是cosnt *CStu。

::v1表示全局变量
函数重载不能重定义默认参数值
int max(int x,int y=0);//原型声明,默认参数值在右面
int max(int x,int y=0){//错误,不能重定义默认参数,尽管与原型声明一样
}

1.18 inline内联函数

作用:减少函数调用开销,代码插入到调用处,导致程序变大,以空间换时间
声明内联函数
inline int max(int a,int b){
return a>b?a:b;
}
调用内联函数
int x=max(3,5);
内联函数展开
int x=(3>5?3:5);
内联函数的声明和定义一般不分开,使用短代码,对编译器的请求,大多数编译器不把带有循环、递归、静态变量等或者代码比较多的函数进行内联编译

1.19 模板

模板:template //主要用在处理不同类型,可以与auto一起用

给予范围的for循环语句
for(元素名变量:广义集合){//循环体
}
“元素名变量”可以使引用类型,直接修改广义集合元素的值;
“元素名变量”也可以使const类型,避免循环体修改元素的值
“广义集合”是范围,也就是一些元素的组合
广义集合的例子
auto a[]{1,3,5,7};//不推荐
std::array<int,4>a2{2,4,6,8};
std::vectorv={42,7,5};
std::vectorstd::strings{“hello”,”world”,”!”};

带有初始换其的ifelse,switch语句
if(int x=1;x){}
else {}//目的是确定x的作用范围,只作用在if代码块中,switch同理
switch(int x=rand()%100;x){
case 1:
//do something
break;
default :
//do something
break;
}

输入信息
auto x{0};
std::cin>>x;

#面向对象:抽象、多态、继承、封装
对象的state是data field、对象的行为是function
创建类的实例推荐类的写法:Circle circle1{};//用空初始化列表初始化对象,调用Circle默认的构造函数
构造函数和析构函数没有数据类型(也没有void)
class A{
public:
A()=default;//强制生成构造函数
};

2.面向对象

2.1 对象拷贝&匿名对象

用原生数据类型定义的是变量,用类定义的是对象
在这里插入图片描述
编码规范3:代表类型的首字母大写,其他混合
成员拷贝:“=”赋值,默认情况下对象的每个数据域进行拷贝
匿名对象:只用一次,比如说Circle{4.2}.getArea();,这种不命名的对象
struct默认是共有的,class默认的是私有的

2.2 局部类&嵌套类

局部类是在一个函数内进行声明
嵌套类是在另一个类中进行声明

2.3 声明与实现分离

.h:类声明,描述类的结构,只能把类的声明放在main文件里,不能把实现放在main.cpp里,#include“”相当于把头文件粘贴到那里
.cpp:类实现,描述类方法的实现,类的实现的格式如下
FunctionType Classname::Functionname(Argument){…}
双冒号是二元作用域解析运算符,简称域分隔符

2.4 内联声明和内联函数

目的:提升C++性能 ,将内联函数体拷贝到当前位置上,避免函数调用的开销
在类中直接定义inline函数,无需写inline关键字,在类里面声明inline函数时,也不需要写inline关键字,在外面实现时,需要写inline关键字。

2.5 .h文件的结构,避免头文件被两次包含

1.#ifndef CIRCLE_H_
#define CIRCLE_H_

#endif //
2.#pragma once;//预处理指令
3._Pragma(“once”);//运算符

#<> 和 “” 的区别
<>是系统提供的头文件
“”是自己创建的头文件

2.6 指针和实体访问类的成员

指针用“->”访问类的成员
实例用"."访问类的成员

2.7 对象数组的声明

在这里插入图片描述

2.8 对象作为参数

注:若是没有修改参数的值,最好用const修饰形参。
注:最好用const修饰函数的返回值类型和参数,除非有特别的目的,比如说移动语义。
邪恶的用法:在函数内new
引用传参的好处:不创建额外的副本,提高了函数的效率
函数最后面加const:告诉编译器该函数不会改变成员变量的值,也提高了代码的可读性

2.9 this指针

*this是实体
Circle set(){return (*this);}//c.set().set();前面的c.set()返回的是一个匿名对象
Circle& set(){return (*this);}//c.set().set();前面的c.set()返回的是c
在这里插入图片描述

2.10 操作对象

returnType getPropertyName()
bool isPropertyName()
void setPropertyName(dataType propertyValue)

2.11 数据域&this指针

若是成员函数里面局部变量与某数据域重名,则局部变量优先级高,同名数据域在函数中被屏蔽。解决数据重名的问题:①this指针指向当前函数的对象,this->radius=radius;②在变量后面加下划线。

C++不能自动推导数组的大小,int n(2);//这是错误的,会被认为是函数

2.12 构造函数

①Circle(int radius_,int re_) : readius{radius_},re{re_} { }
②(重要)内嵌对象(Time)必须在被嵌对象(Action)的构造函数的函数体执行之前就要构造完成,如下:
class Time{/code omitted/};
class Action{
public:Action(int hour,int minute,int second): time{hour,minute,second}{}
private:Time time;
};
默认构造函数(无参调用):没有参数、参数有默认值
默认构造函数的作用:①对象成员/内嵌对象没有显示初始化,如下,Circle类的构造函数会被X()调用,若Circle类中没有无参构造函数,会报错。②在列表初始化中手工构造对象。\

①class X{
privat:Circle c1;
public:X() { }
};
②class X{
privat:Circle c1;
public:X():c1{}{ }
};

2.13 初始化

如何初始化:就地初始化、ctor初始化列表、在ctor函数体中为成员赋值
优先级:在ctor函数体中为成员赋值>ctor初始化列表>就地初始化
如果同时有就地初始化和ctor初始化裂变,则就地初始化忽略不执行

2.14 字符串类string

2.14.1常用函数

在这里插入图片描述
index:从第index索引开始
n:之后的n个字符
s.erase(int index);//删除index后面所有的字符

s.find_first_not_of(" \r\n\r");//第一个不是什么的字符
s.find_last_not_of(" \r\n\r");//最后一个不是什么的字符
/*清除前面和后面的空白字符:*/
s.erase(0,s.find_first_not_of(" \r\n\r"));
s.erase(s.find_last_not_of(" \r\n\r")+1); 

2.14.2字符串转换

std::stoi(“1024”);//输出为整数1024
std::string(1024);//输出字符串1024

2.14.3字符串运算符

在这里插入图片描述

2.14.4 string和string_view的区别

string可以修改,涉及堆内存管理
string_view只能读取,不涉及堆内存管理

2.15 C++中的数组类(-std=c++17)

2.15.1定义及好处

是一个容器类,所以有迭代器(可以认为是一种访问成员的高级指针)
可以直接赋值
知道自己的大小:size()
能和另一个数组交换内容:swap()
能以指定内容填充自己:fill()
取某个位置的元素(做越界检查):at()

2.15.2使用格式

#include
array<数组类型,数组大小> 数组名字{值};

可以推导数组的大小
std:array a{1,3,5};//std:;array<int 3> a{1,3,5};

数组排序:std::sort(a.begin(),a.end());
二维数组:std::array<std::array<int ,3>,3> a{};
a作为参数传过去void f(array<int,3> b){},在函数体内改变b的值,不改变a的值

2.15.3 std::array的成员函数

在这里插入图片描述

2.16关键字constexpr

定义:常量表达式是在编译器就能计算其结果的式子,就是编译器去执行这段代码(伪装操作系统)。
作用:C++数组的大小要求是编译器的一个常量,const修饰的对象未必是编译器常量,可能是运行期的常量。constexpr定义的是编译期的常量。

constexpr int max(int a,int b){
return a>b?a:b;
}//要求也要给a和b传递编译器的常量

const vs.constexpr
const告知程序员const修饰的内容不能修改,主要是帮助程序员避免bug
constexpr被要求用在所有const experssion的地方,主要是让编译器能够优化代码,提升性能。

2.17 断言/c++11的静态断言

断言是一条检测假设成立与否的语句,假设成立,什么也不发生;假设不成立,中断程序的运行
assert 断言&&static_assert断言
assert
定义:C语言的宏,运行时检测,依赖于NDEBUG宏
使用:调试(debug)模式下,编译器不会定义NDEBUG宏,assert()宏起作用;发行(release)模式编译下。编译器定义NDEBUG宏,assert不起作用,可以手动#define/#udef NDEBUG
用的话要包含头文件,以调试模式编译程序
assert(bool_expr);//bool_expr为假则中断程序
作用:相当于断点或者单步调试
static_assert
使用:static_assert(bool_constexpr,message)
bool_constexpr:编译期常量,可以转换成布尔类型
meeage:字符串字面量,是断言失败时显示的警告信息
作用:编译时断言检查
//确保编译仅在32为系统工作,不支持64位
static_assert(sizeof(* void)==4,”64 bits code generation is not supported”);
使用:常在模板编程中,对写库的作者用处大
若某些情况是你预期中的,那么用错误处理;若某些状况永不该发生,用断言。

2.18 声明&&定义

声明:为了引入一个标识符,并描述他的类型,编译器需要该“声明”,以便识别在他处使用该标识符。(在其他.cpp文件内的变量)
例子:extern int a;int max();
定义:实例化/实现这标识符,链接器需要“定义”,以便将对标识符的引用链接到标识符所表示的实体。
例子:int a=0; int max(){}
定义和声明的区别:
①定义有时可以取代声明,反之则不行。②标识符可以别声明多次,但是只能被调用一次。③定义通常伴随着为标识符分配内存。
总结:
声明:某个地方有a。
定义:a在这,它长这样。

2.19 代理构造&委托构造

一个构造函数可以调用另外一个构造函数
委托构造函数():被委托构造函数() { }
要避免递归调用目标构造函数,递归调用就是环形调用
好处:可以让程序员少写一些代码,程序更清晰一些

2.20 不可变对象&&类

不可变对象:对象创建后,内容不可改变,除非通过成员拷贝
不可变类:不可变对象所属的类
不可变类必要条件:数据私有、无改变数据的函数,若是getstr返回成员的指针或引用,就有可能改变对象
变量名必须混合大小写,且以小写字母开头
不可变类的充分条件:数据私有、无该百年数据的函数、没有函数返回对象成员的指针或引用

枚举
enum clas Gender{
famle,
male,
};

2.21 实例成员&静态成员

在类的内部,用static声明。不绑定在类的实例上,不一定要用对象的名字访问,可以用类的名字访问。(类外定义,类内声明)
静态成员的定义:
1.声明为constexpr类型的静态数据成员,必须在类中声明并初始化,可以不在类外定义。
2.inline和const int可以在类中声明并初始化。
3.其他情况必须在类外定义并且初始化,且不带static关键字。
静态数据成员有一个静态存储期或者C++11的线程存储期
静态存储期:对象的存储在程序开始时分配,在程序结束时回收;只存在对象的一个实例;静态存储期对象未明确初始化会被零初始化。
类外定义,类内声明:
class a{
public:
static int number;
}
int a::number;//位于全局/静态区

2.22 析构函数&构造函数

析构函数:对象销毁时使用,C::C,无参数,默认析构函数:C::C(),没有显示声明编译器自动生成,不能重载。
构造函数:对象创建时使用,C::C(argument),默认狗赞函数:C::C(),没有显示声明编译器自动生成,可重载
C::~C()=default;//生成默认构造函数
C::~C()=delete;//不能生成默认构造函数

2.23 友元

私有成员无法从类外访问,但是有时又需要授权某些可信的函数和类访问这些私有成员
用friend关键字声明友元函数或者类
class M{
private :
int a;
public:
friend class kid;//可以在类kid里面访问M的a成员
friend void print(M &m);//可以在函数里访问M的a成员
};

2.24 拷贝构造函数

拷贝构造:用一个对象初始化另一个同类对象
声明拷贝构造函数:Circle(Circle&);Circle(const Circle&)
调用拷贝构造函数:Circle c1(5.0);
Circle c2(c1);
Circle c3=c1;//如果c3没有被初始化,执行拷贝构造。如果c3被初始化了,执行赋值
Circle c4={c1};
Circle c5{c1};
隐式声明的拷贝构造函数:
程序院不编写拷贝构造函数,编译器会自动生成一个。自动生成的叫做隐式声明/定义的拷贝构造函数。隐式声明/定义的拷贝构造函数简单的将数据域的内容复制过去。

2.24.1浅拷贝&深拷贝

浅拷贝:数据域是一个指针,只拷贝指针的地址,不拷贝指针指向的内容。创建新对象的隐式声明/默认拷贝构造函数;为已有对象赋值的默认赋值运算符。
深拷贝:拷贝指针指向的内容。自己定义拷贝构造函数,重载赋值运算符,重新在堆区新申请内存。

2.25 vector类

数组:用数组存放数据时,大小不变
vector对象的容量可自动增大
vector类的函数:
在这里插入图片描述
例子:
vector iv{-2,0};
for(int i=0;i<10;i++)
{
iv.push_back(i+1);
}
编码规范25:迭代变量名应该用i,j,k等

2.26 字符串字面量

“原始/生”字符串字面量
语法:R”delimiter(raw_characters)delimiter”
auto hello=”hello”s;//hello是std::string类型
auto hello=std::string{“hello”};//同上
auto hello=”hello”;//这是const char*类型

2.27 栈

栈是一种后进先出的数据结构
进栈:push
出栈:pop
for(auto& i:element){
//遍历每个元素,i是element数组里元素的别名
}

2.28 结构化绑定声明

结构化绑定声明是一个声明语句,意味着声明一些标识符并对标识符进行初始化,将指定的名字绑定到初始化器额子对象或者元素上。
形态:
cv-auto &/&&(可选) [标识符列表]=表达式;
cv-auto &/&&(可选) [标识符列表]{表达式};
cv-auto &/&&(可选) 标识符列表
cv 可能是consta / valitile 修饰auto的关键字
标识符绑定数组规则:标识符的数量必须等于数组的元素数量,标识符类型和数组元素个数相同。
int ptrint[]{1,2,3};
auto [a1,a2,a3]=ptrint;//a1是ptrint[0]的拷贝,其他一样
const auto a1,a2,a3;//a1是ptring[0]的只读拷贝,其他一样
auto &[a1,a2,a3]{ptrint};//a1是ptrint[0]的别名,其他一样
标识符绑定类/结构体的非静态数据成员规则:数据成员是公有的、标识符数量等于数据成员的数量、标识符类型与数据成员类型相同。
class C{
int i{420};
char ca[3]={‘O’,’K’,’!’};
};
int main(){
C c;
auto [a1,a2]{c};
cout<<a1<<a2<<endl;
}

2.29 继承

父类:自行车(抽象)
子类:山地自行车、双人自行车(具体)
继承链的类的对应叫法:基类(父类)、派生类(子类)
子继承父,父泛化子,子类中具有父类中的大部分成员
final特殊标识符,表明类不能在被继承
继承的格式:class Circle:public Shape{…};
构造函数可写为:Circle::Circle(int r,int color,int filled):Shape{color,filed}{…}

2.29.1性质

派生类不继承的特殊函数:析构函数、友元函数
继承基类构造函数:using A::A,继承基类所有构造函数,相当于派生类B和基类A:B():A(){};不能仅继承指定的某个基类构造函数。
调用继承的构造函数:
struct A{
A(int i){}
A(double d,int i){}
};
struct B{
using A::A;
int d{0};
};
调用继承的构造函数时相当于B(int i):A{i}{}
调用基类的构造函数

若派生类也需要初始化,则可以在派生类构造函数中调用基类构造函数。
struct A{
A(int i){…}
A(double d,int i){}
};
struct B:A{
using A::A;
//using A::A
B(int i):A{i},d{0}{}//有了B(int i),就不继承A(int i)了
};

列表初始化调用构造函数的顺序:先调用基类的,再嵌套,最后本函数体

若基类的构造函数未被显示调用,则调用默认构造函数(在初始化列表的位置)
编码规范32:类应该在头文件中声明并在源文件中定义,文件名要与类的名字相同。
编码规范49:class 的变量成员不应该是public
构造函数调用次序:父在前,子在后,内嵌在中间
析构函数调用次序:子在前,父在后,内嵌在中间
局部变量在栈,先进后出,先创建的后销毁
销毁时:有内嵌对象,先调用本类的析构函数,在调用内嵌的析构函数,最后调用父类的构造函数

名字隐藏&重定义

派生类被视为内部作用域,基类被视为外部作用域,内部作用域的名字隐藏外部作用域的名字。设计原因:避免潜在的危险行为,下面的代码会报错

class p{
public:
 void f(){}
};
class c:public p{
public:
void f(){}
};
int main(){
c c1{};
c1.f();//报错,以免程序员不清楚调用的哪一个f(),如果写上 using p::f;将基类的对象显式的在派生类中声明,就可以了
}

重定义函数
在基类的函数不能输出派生类的信息,因此需要重定义函数,要求两个函数的定义完全相同:名字、参数、数据类型等。例子:如果Circle是子类,Shape是父类,调用Circle类中的函数是c.to_string();如果要调用父类中的函数,要用c.Shape::to_string();

重定义和重载的区别
重载是多个函数名字相同,但是参数不同。
重定义是出现在基类和派生类中,要求名字、参数等完全相同,在基类和派生类中分别定义。

枚举:
enum class Color{
white,black,red,
}
int main(){
Color c{Color::black};
}

2.30 多态

定义:不同的实体/对象对同一消息的不同响应。
种类:重载多态、同名重定义函数
实现多态:Binding(联编)确定具有多态性的函数调用哪一个函数的过程。Static Binding 静态联编,在程序编译时就确定调用哪一个函数,例如函数重载。Dynamic Binding 动态联编,在程序运行时才能确定调用哪一个函数,即运行时多态。
实现运行时多态:virtual function 虚函数,在基类函数体面前加一个virtual关键字,在派生类中会override,即重定义一个虚函数。在基类定义一个虚函数,那么派生类的同名函数也自动成为虚函数,不需要加virtual关键字。(虚函数的传递性)
虚函数:虚函数有一个虚函数表,在调用虚函数时需要执行动态联编,需要额外的逻辑开销。
隐式类型转换:将派生类的地址转化为基类形式的指针,这是安全的。函数的输出跟函数的对象是直接相关的,跟指针类型无关。如果没定义virtual,会调用指针类型的函数。
用途:用父类的指针访问子类的对象。
override加载派生类对象后面,可以告诉程序员这个函数时覆盖子类的函数,如果没有覆盖,会提醒程序员。

运行时多态总结:

基类与派生类有同名函数

1.通过派生对象访问同名函数
静态联编,对象是什么类型,就调用什么类型
2.通过基类对象指针访问同名函数
静态联编,调用基类的函数,指针是什么类型,就调用什么类型
3.通过基类对象指针或引用访问同名虚函数
动态联编,指向/引用什么类型,就调用什么类型,即函数虚,不看指针/引用,看对象

override和finnal

override:指定一个虚函数去覆写另外一个虚函数,只能在类内定义,主要作用是避免程序员因为函数名字错误写出bug
注:const也是函数的一个属性,函数在后面写了const和没写是不一样的函数
final:指定派生类不能被覆写虚函数,可以覆写基类虚函数
注:非虚函数不能被覆写或者final

2.31 访问控制

protected:保护属性的数据或函数可以被派生类成员访问。
class B:public A{public:};//public A是派生方式(可以访问public和protected),public:是访问属性

2.32 继承方式:

1.公有继承 class B:public A{};
基类的数据、函数到派生类属性不变
派生类的函数可以的访问基类的public、protected,不能访问private;派生类以外的函数不能访问protected、private
2.私有继承class B:private A{};
基类的数据、函数到派生类变为private
派生类可以访问基类的public、protected,不能访问private;派生类以外的函数都不能访问。
3.保护继承class B:protected A{};
基类的数据、函数到派生类public变为protected,其他不变
派生类可以访问基类的public、protected,不能访问private;派生类以外的函数都不能访问

2.33 抽象类和纯虚函数

派生类时,新类会越来越具体;沿着派生类像父类移动,越来越抽象。
类太抽象以至于无法实例化的叫做抽象类。抽象函数要求子类去实现它,包含抽象函数的类叫做抽象类。
virtual doublid getArea()=0;//在shape类中
Circle子类必须实现getArea()纯虚函数才能实例化
斜体表示抽象类、纯虚函数、虚函数,纯虚函数不能定义函数体。

2.34 动态类型转换

dynamic_cast运算符
沿继承层次向上、向下、侧向转换类的指针和引用
转指针失败,放回nullptr
转引用失败,抛出异常
实例

void printObject(Shape &S){
cout<<S.getArea()<<endl;
Shape *p=&S;
Circle c=dynamin_cast<Circle*>(p);
if(c==nullptr){
cout<<S.getdimeter()<<endl;
}
else cout<<S.getRadius()<<endl;
}

2.35 向上/下转型(基类是上、派生类是下)(可以理解为向上赋值、下向赋值)

向上转型,将派生类指针赋值给基类
向下转型:将基类指针赋值给派生类
·上转可以不适用dynamic_cast而隐式转换
Shape *s=new Shape{};
Circle *c=new Circle{};
s=c;//安全
·下转必须显示转换
Shape *s=new Shape{};
Circle c=new Circle{};
c=dynamic_cast<Circle
>(s);//必须显示转换

2.36 基类对象和派生类对象的互操作

Shape是基类,Circle是派生类
Shape &S=C;//OK,截断
Circle &C=S;//errot,不能将基类加长
S=C;//OK,截断
C=S;//error,不能将基类加长
注:可以将派生类对象截断,只保留继承来的信息;不能将基类对象加长,无中生有新的信息。

typeid
#include
运行时查询类型信息
typeid运算符返回一个对象type_info的引用
typeid(AType).name();//返回一个char*类型的指针,不是string
auto &t=typeid(a);//返回的是对象的引用
RTTI:run time type indentification,通过运行类型识别,程序能够使用基类的指针和引用所指的实际派生类型类型,RTTI提供两个操作符:typeid、dynamic_cast。

3.文件的输入输出流

文件系统库
#include//C++17支持
std::filesystem
给命名空间定义一个别名:namespace fs=std::filesystem,定义变量,fs::path p1{“cpp.txt”};,fs::path p2{R“d:\cpp\hi.cpp”}’

3.1 path class

+path(string); 构造函数
+assign(sting):path&,为路径对象赋值
+append(type p):path&,将p追加到路径后,type是string、p、const char*。等价于/=;自动追加目录分隔符
+contact(type p):path&,将p追加到路径后,type是string、p、const char*。等价于+=;不自动追加目录分隔符
+clear();
操作符:/= ,+=, /
p=p/R”c:/users”/”dic”
fs::space(path).capcity;//当前分区磁盘的大小
fs::space(path).free;//当前分区磁盘的剩余大小
fs::file_size();//返回文件的大小
创建路径:fs::path p1{R” C:\Users\lh\Desktop\相册”};fs::path{“C:/Users/lh/Desktop/相册”};

3.2 文件i/o流

文件输入输出流:fstream=ifstream+ofstream
读文件:>>,get(),get(char),get(char*),getline(),read(char*,streamsize)
写文件:<<,put(char),put(int),write(const char*,streamsize),flush()
状态测试:cof(),bad(),good(),fail()

读取数据

auto p=cin.rdbuf();//输入缓冲区的指针
cin.peek();//在输入缓冲区中读取数据,但是不擦除
cin.get();/在输入缓冲区读取字符并擦除
p-in_avail();//在输入缓冲区的字符数量

向文件中写数据,write data to file

如果文本中有数据,不做特殊处理时,会把文件内容清除
1.创建ofstream实例,如果带路径参数,在ofstream的构造函数中打开文件,如果不带参数,调用open()打开文件,2.使用流插入运算符”<<”写数据,3.调用close(),或由ofstream析构函数关闭文件。

#include <fstream>
#include<filesystem>
#include<iostream>
using namespace std;
namespace fs=std::filesystem;
int main(){
fs::path p{“sourses.txt”};
ofstream optput{p};
output<<”lh:”<<95<<endl;
output.close();
}

###从文件中读取数据
ifstream从文本文件中读取数据,需要检测文件是否被成功打开
1.创建ifstream,判断有没有path路径,如果没有被打开程序结束,2.文件被打开,用流提取运算符”>>”读数据,3.调用close()关闭文件
要了解数据格式
要判断是否打开成功,output.fail();或者input.fail();,返回true,文件未打开,判断是否到了文件末尾,input.eof()==false//表示未读到文件末尾,input.get();//读出一行
input.get()返回的时int,要进行强制类型转换,static_cast(input.get());

//输入读取例子
#include <fstream>
#include<filesystem>
#include<iostream>
using namespace std;
namespace fs = std::filesystem;
int main() {
	fs::path p{ "sourses.txt" };
	ofstream output{ p };
	if (output.fail())
		exit(0);
	output << "lehua" << " " << 95 << endl;;
	output << "wanghan " << " " << 90.5;
	output.close();
	ifstream in{ p };
	if (in.fail())
		exit(0);
	char x;
	while (!in.get(x).eof()) {//避免最后一个数字被重复读取
		cout << x;
	}

	in.close();
}

3.3 格式化输出

setw manipulator(“设置域宽”控制符)
头文件
setw(n);//设置位宽,默认右对齐,只对输出第一个数据有效
setpricision(n);//设置浮点精度,n代表数字的总位数,不包括小数点,四舍五入,n等于0时,根据编译器而异
setfill©;//c是填充字符,cout<<setfill(‘’)<<setw(5)<<a<<endl;a=1时,输出***1
fixed//将浮点数以定点数形势输入输出(小数点后有效数字位数个数等于setpricision 中n的个数)
showpoint//将浮点数带小数点和结尾0的形式输入输出,即使该浮点数没有小数部分(小数点后面4位数字)
left、right//输出左对齐or右对齐
hexfloat/defaultfloat;//十六进制浮点数,还原浮点数
get_money(money);
put_money(money);//从流中读取货币,或将货币输出到流
get_time(tm,format)
put_time(tm,format)//从流中读取日期,或写入日期

3.4 输入输出流的函数

>>用空格或、tab或者换行分割

getline()

成员函数:getline(char* buf,int size,char delmiter);//buf存放读取的,size最大,delmiter分隔符,默认空格,istream/ifstream.getline(p,50,’#’);
如果使用array<char,30> name;的格式,&name[0]时char*,name是array类型,string s;&s[0]时char *类型
非成员函数(string库里):getline(istrea& is,string str,char delmiter);
in.getline(&name[0],size,’#’);

get()&put()

get()读流里的单个字符
int istream::get();
istream::get(char &c);
接受时:char c=static_cast(cin.get());
put()写流里的单个字符
ostream::put(char c)

flush()

针对带缓冲的输出流使用,将输出流缓存中的数据写到目标文件中
cout.flush();//其他输出流对象也可以调用

3.5 fstream

fstream::seekg();//将光标移到文件头
fstream=ifstream+ofstream
创建fstream对象时,要指定一个file open mode(文件打开模式),file open mode时在ios_base定义了一个数据类型。
ios::in 打开文件读数据
ios::out 打开文件写数据
ios::app 把输出追加到文件末尾。app=append
ios::ate 打开文件,把文件光标移到末尾。ate=at end
ios::trunc 若文件存在,舍弃其内容。这是ios::out的默认行为
ios::binnary 以二进制方式对文件进行读写
用位或操作可以将几种模式组合在一起
比如打开文件name.txt追加数据
stream.open(“sourses”,ios::out|ios::app);

3.6 二进制输入/输出

存储199
十进制存储 31 39 39 //占三个字节
二进制存储 C7 //占一个字节
文本模式的读写:>>,get(),getline(),<<,put()
二进制模式的读写:read(),write()

write()函数

返回输出流运算符的引用
将字符串写入文件中:
char s[]=”123”;
fstream fs(“s1.txt”,ios::binary|ios::trunc);
fs.write(s,sizeof(s));
将非字符数据写入文件,将数据转换为字节序列,即字节流,再用write函数将字节序列写入文件

static_casr//用于基础类型的数据转换
dnamic_cast//继承链上基类和派生类的指针或引用的转化
reinterpret_cast//1.将一种类型的地址转换为另外一种类型的地址(指针),将地址转换为数值,比如转换为整数,reinterpret_cast<data_type>(address)

对于二进制读写来说,datatype就是chat ,注意char是读到’\0’为止
int a=10;
char* p1=reinterpret_cast<char*>a;

read()函数

格式read(char* s,std::stream size);
读取字符串
fstream bio{p,ios::binary|ios::in}
char* s[10]{};
bio.read(s,5);
s[5]=’\0’;
cout<<s;
读取非字符串类型
int value{0};
bio.read(reinterpret_cast<char*>(value),sizeof(value));
cout<<value;

3.7 随机访问文件

文件位置指示器,file positioner,fp,其他说法:文件指针、文件光标

文件是由字节序列构成的,一个特殊标记指向其中一个字节
读写操作都是从文件位置指示器所标志的位置开始的
打开文件 ,fp指向文件头,读写文件时,文件指示器会向后移动到下一个数据项。
aFileStream.get()->fp=fp+1;由于get取的是字符,就会向后移动一个字节

随机访问文件

随意访问意味着可以读写文件的任意位置
步骤:我们能知道文件定位器在什么位置,我们能在文件中移动文件定位器
相关函数:
获知文件定位器指到哪里:
tellg();//tell是获知,g是get表示读文件
tellp();//tell是获知,p是put,表示写文件
移动文件定位器到指定位置:
seekg();//seek是寻找,g是get表示读文件
seekp();//seek是寻找,p是put表示写文件
stream& seekg/seekp(pos_type pos);//绝对位置
stream& seekg/seekp(off_type off,std::ios_base::seekdir dir);//相对与dir的位置,dir是文件定位方向类型,有三种类型,std::ios_base::beg//流的开始,std::ios_base::end//流的结束,std::ios_bas::cur/当前位置
seekg(42L);//将文件指示器移动到第42个字节处
seekg(10L,std::ios_base::beg);//将文件指示器移动到从文件开头算起,第十个字节处
seekp(-10L,std::ios_base::end);//将文件指示器移动到从文件末尾算起的,倒数第十个字节处
seekp(10L,std::ios_base::cur);//将文件指示器移动到从当前位置算起,第十个字节处

4.运算符重载

4.1 运算符重载与平面向量类

运算符和函数

与对象一起用的运算符
string s1{},s2{};
cout<<s1+s2;
array<int,3> a{};
a[2]=0;
std::filesystem::path p{};
p=p/”c:”/”user”/”cyd”;
运算符可以看作是函数,运算符只能重载不能新定义
函数式编程语言的观念:一切皆函数;Haskell可以定义新的函数

平面向量类

向量数据成员double x_,double y_;或者std::array<double,2> v_;
Vec2D类
注:函数参数设置为引用,主要是为了避免对象拷贝造成的开销

double atan(double x);//返回x的正切
double sqrt(double x);//返回x的开方
double pow(double b,double exp)//返回b的exp次方
编码规范12:一般变量的名字应该与变量的类型相同,比如void connect(Database* database)
视图->类视图->在工程上点鼠标右键->类向导->添加类->添加成员变量->添加自定义->方法->添加方法
函数返回本身类型加引用&,return (*this);

可重载运算符:1.类型转换运算符,2.new/delete,new[]/delete,3.””_suffix用户自定义运算符,4.一般运算符
不可重载运算符:.类属关系运算符 .*成员指针运算符 ::作用域运算符 ?:三元运算符 #编译预处理运算符
运算符重载的限制:优先级和结合性不变,不能创造新的运算符

operator function(运算符函数)
cout<<v1<c2;等将于cout<<v1.operate<(v2);//相当于调用函数
内嵌对象类型不能重载运算
内嵌对象与对象相加,需要定义友元操作函数friend operator+(double z,Class c);在外面定义的时候就不用写friend关键字

this指针:1.不用被定义,2不能取地址,3不能赋值(改变指针指向),4.指向当前对象
&作为形参:不用传递形参、少用栈区内存、提高函数调用效率
确定运算符函数的调用形式:
注:@是运算符
在这里插入图片描述

左值、纯右值、将亡值

放在等号左边的是lvalue、放在等号右边的是rvalue,lvalue可以当作rvalue使用。
移动语义:提高程序的运行效率和性能
左值:指定了一个函数或者对象,它是一个可以取地址的表达式
左值例子:解引用表达式p,字符串字面量”abc”,前置自增自减表达式,赋值和复合表达式(x=y,m=n)
纯右值:不和对象相关联的值(字面量)或者其求值结果是字面量或者一个匿名的临时对象
右值例子:除字符串字面量以外的字面量比如32 ’a’,返回非引用的函数,后置自增/自减运算(返回结果是临时对象),算数/逻辑/关系表达式(a>b,a||b,a==b),取地址(&x)
将亡值:将亡值也指定了一个对象,是将一个纯右值转化为右值引用的表达式
int a=1;int& b=a;b是一个左值引用
const int& lvr5{22};//常量左值引用可以引用纯右值
int& lvr6{22};//错误,非常量左值引用不能引用纯右值
int&& lvr1{22};//右值引用可以引用纯右值
int&& lvr2{f()};//右值引用可以引用函数返回值
lvr1=++lvr2;//经过右值引用可以修改

下标重载,数组越界抛出异常:throw std::out_of_range(“you out l ”)
@obj,遇到这种情况会调用类的成员函数或者类的友元成员函数

前置/后置、++/–

前置++/–重载无参数,返回引用类型
后置++/–重载有参数—“dummy(大米)”参数
在这里插入图片描述
前置自增自减,运算返回引用;后置自增自减,先保存(匿名临时),再运算

4.2 重载操作流操作符

重载流提取/流插入运算符(二元运算符)

<<:将信息输出到ostream对象中
>>:从istream对象中提取信息
为了习惯,只能重载为友元函数,如果不修改值,要const Class& c;
std::ostream& operator<<(std::ostream os,const Class& c);
std::istream& operator>>(std::istream is,Class& c);

##重载对象转换运算符
将一个对象Vect2D转换成double value,相当于求向量的长度
Vect2D::operator double(){
return sqrt(xx+yy);
}
用法
Vect2D v{3,4};
double d=v+5.1//隐式类型转换,d是10.1
double e=static_cast(v);//显示类型,强制转换,得到e是5.0

重载赋值运算符

默认一对一拷贝(浅拷贝)
重载赋值运算符 Employee& operator=(cosnt Employee& e);
初始化调用的是构造函数,赋值调用的赋值操作

重载运算符总结:

1.重载运算符不能只是基础数据类型(防止用户修改标准运算符的性质
),必须要有一个class类型使用
重载运算符可以作为成员函数重载、友元函数重载(友元函数可以指定两个对象的次序)
前自增可以取地址,返回引用,可以作为左值

更多编码规范

25.ijk作为循环变量,jk作为嵌套
41.包含语句应排序并分组,排序时,按其在系统的层次位置,较低层次的在前。用空行分隔分组的包含语句。
低:stl->qt->程序员自定义
包含文件的路径中,一定要使用绝对路径
42.包含语句一定要放在文件的顶部

5.异常处理

5.1 异常处理(exception-handling)概述

异常处理的必要性:计算两个数的除法,如果0作为除数,就会中断程序的运行
处理办法:1.if判断;2.异常处理
try-throw-catch
throw之后的语句不会执行
catch(type e){}
catch如果能抓住throw扔出来的东西,就处理下面的代码块。type类型匹配的时候可以抓住。如果抓不住,程序就没人管了,可能会崩掉。
异常处理机制的优点:异常处理实际上时对程序执行的流程进行控制。
简单问题简单办,复杂问题异常办。
复杂问题举例:函数、函数嵌套里出现异常

5.1.2 异常匹配和异常类

catch(type);//可以只保留一个类别
e.what();//what是exception里面的一个虚函数,返回char*类型的指针
为了可以提取更多的信息,一般使用异常类

5.1.3 内建异常类

#include
class exception{
exception();//构造函数
virtual const char* what();//返回解释性字符串,what返回的指针保证在获取他的对象被销毁前合法
}
exception是所有异常的基类
标准库中的异常类
logic_error:invalid_argument/domain_error/length_error/out_of_range/future_error(涉及多线程中异步执行和共享状态)/bad_optional_access。逻辑错误在程序运行前能够被检测到。
runtime_error:rang_error/regex_error(正则表达式库中的错误)/system_error::ios_base::failure,filesystem::filesystem_error。运行错误只有在运行时才能检测到。
bad_typid:typeid(*0),对某些类型进行查找时会抛出异常,bad_cast,基类派生类指针转换时发生错误
bad_alloc:内存不足时抛出
自定义异常类,需要继承exception基类,才能捕获到exception
捕获多种无关异常:实现方法,多次用catch
捕获派生异常例子:
class MyException:public logic_error{};
try{
throw MyException();
}
catch(logic_error& e)//既可以捕获基类的异常,也可以不过派生类的异常
{
MyException p=dynamic_cast<MyException>(&e);
if(p!=nullptr){
cout<<”this is MyException”<<endl;
}
}
在继承链上出现的异常类catch次序:派生类的catch块放在前面,基类的在后面

e.what()的输出是传进去的字符串

5.2 noexcept

noexcept指明函数是否抛出异常,放在函数后面
如果不抛出异常,可以做编译器优化
如果抛出异常,也不在说明抛出异常的类型
用法,作为声明符放在函数的右边/noexcept布尔表达式
void foo() noexcept{}等价与void foo() noexcept(true){}函数中不能出现异常,如果出现异常,函数会调用std::teminate,也就是说,程序会就此终结。
void foo() {}等价与void foo()noexcep(false){}
noexcept不能区分重载函数(对比:const 能区分重载函数)
noexcept可以当作运算符来使用:bool noexcept(excepression)
void may_throw();
void no_throw() noexcept;
noexcept(may_throw);返回false
noexcept(no_throw);返回true
std::boolalpha布尔表达式,以字符形式输出
异常传播,前提:有一堆的函数调用,每个/大多数函数体中都有try-catch,内层函数抛出异常
如果当前异常没有被处理,返回调用当前函数的函数(上一级函数),如果没有上一级函数,终止程序

5.3 重抛异常

在catch抓住了异常,但是重新把他抛出来,原因:这个catch没办法处理或没办法完全处理这个异常;想告诉他的调用这发生了一个异常
方法:throw;//抛出当前异常

5.4 何时使用异常

当一个外部问题阻止程序的运行,抛异常

从服务器接收到非法数据
磁盘满了
宇宙射线阻止你查询数据库

如果函数无法完成他所告知的功能并建立正常的后置状态,抛异常

构造函数失败,例如vector构造对象,但对象占内存太大无法建立,抛出异常

5.5 何时不用异常

只发生在单独函数的简单错误不抛出异常

代码中有逻辑错误,不用异常

可以用assert()中断程序的执行进行调试

不要用异常来做流程的控制

不用throw结束for循环

实时系统中不用异常(航天飞机控制程序,生命维持设备)

5.6 编码规范

23.表述对象数量的变量名,前面要加上n。nPoints,nLines
24.代表实体编号的人变量名后面应加后缀No。tableNo,employeeNo
替代方法:在变量前面加上i。iTable,iEmoloyee。这样让他们的名字变成了一个迭代器(iterator)

6.模板

C++的泛型编程,即编译时多态,是由元编程实现的,也就是有代码模板生成代码
编程:处理数据。元编程:处理程序。
初识模板:
GenricType maxValue(const GenricType& a,const GenricType& b){return a>b?a:b;}

6.1函数模板

1.用法:
template//模板前缀,T叫做类型参数
returnType functionName(T p1,…){//函数的参数中要求T出现的次数不少于1次
函数体
}
//T可以出现的位置:返回值、参数、函数体内的局部变量
2.声明类型参数T:
①:描述性强,较好
②:易与内混淆,不推荐
①②是等价的
3.多个类型参数
template<typename T,typename S>
auto add(T t,S s)//C++14
{
return (t+s);
}
编码规范:8.用于表示模板类的名字应该使用大写字母
4.函数模板实例化
显示实例化explicit
隐式实例化implicit
模板多态是编译时多态,也叫静态联编
①显示实例化

template<typename T>
void f(T t){
cout<<t;
}
//方法1
void f<double>(double);//实例化,编译器生成代码
/*
void f(double t){
cout<<t;
}
*/
//方法2
void f<>(char);
//方法3
void f(int);

②.隐式实例化
编译器查看函数调用,推导模板实参,实现隐式实例化
PS:推导模板实参:T是形参,int是实参,将int赋给T

#include<iostream>
template<typename T>
void f(T t){
cout<<t<<endl;;
}
int main(){
f<double>(1.1);//方法1,实例化并调用f<double>(double )
f<>(‘a’);//方法2,实例化并调用f<char>(char)
f(1);//方法3,实例化并调用f<int>(int)
void (*ptr)(std::string)=f;//方法4,实例化并调用f<string>(string)
return 0;
}
//指针调用函数,指明参数格式和类型
//ps:无法重载仅返回值区分的函数

5.实例函数实例类
实例函数:从模板实例出来的函数
实例类:从类中实例出来的类
栈区是高地址往低地址分配内存

template<typename T>
T max(T t1, T t2)
{
	return t1 > t2 ? t1 : t2;
}

注意:
max(“ABC”,”ABD”);//返回的是ABC,因为传入的字符串常量,T的实参是const char*,前面的地址比后面的地址高,两个地址相比较,比较的是高低
#include<string>
max(“ABC”s,”ABD”s);//返回的是ABD,传入的是字符串类型
PS:模板函数的声明时实现要放在一起

6.设计泛型函数
先设计一个非泛型函数->调试->转化为泛型函数
如何泛型化?
函数处理哪些数据、数据的类型是什么、将数据类型转换成一个类型参数

6.2类模板

类模板是将类中的某些类型变为泛型,从而定义了一个模板
数据域成员:可以被成为泛型数据
函数成员:返回值类型、参数类型、局部变量可以成为泛型
类模板的语法:

```C++
template<typename T>
class Stack {
public:
	Stack();
	bool empty();
	T peek();
	T push(T value);
	int getSize();
private:
	T elements[100];
	int size;
};
//声明函数
template<typename T>
bool Stack<T>::empty() {
	函数体;
}
template<typename T>
T Stack<T>::peek() {
	函数体;
}
//ps:声明一个函数就需要写一个template<typename T>

类模板的实例化
显式实例化:template class Stack
隐式实例化:Stack S{};

c++17引入了自动推导
vector v{1,3,4};//自动推导为vector

模板参数和模板继承

默认类型和非默认类型

指定默认类型:
template
class Stack{
}
Stack<> s;//处理整数类型的Stack,方便使用者,只局限于类中用使用
非类型参数
template<typename T,int capcity>
class Stack{

private:
T elements[capcity];
int size;
}
Stack<char ,100> charStack;

模板和继承

类继承模板实例
模板继承类
模板继承模板
注意,类不能继承模板

何时使用模板

1.使用别人写好的模板库
2.对不同的类型进行相同的处理
3.过量使用模板不好
在c++库里较多的运用了泛型编程,较少的用面向对象的继承和运行时多态。异常、字符串、IO流较多的用了继承。

7.标准模板库

STL组成部分:容器、迭代器、算法、函数对象、空间分配器
容器:保存数据。容器分类:顺序容器(线性结构,有vector、list’、deque)、关联容器(可以快速定位元素位置的非线性数据结构,主要作用是存储键值对,有set(集合)、multiset(可重复)、map、multimap)、容器适配器(是顺序容器的受限版本,用于处理特殊情况,stack、queue、priority_queue)。
迭代器:遍历容器中的元素,for(int i=0,i<10;i++),实现:将*,->,++等与指针相关的操作进行重载。每个容器都有自己专属的迭代器,只有容器才知道如何遍历自己的元素。迭代器是一种泛型指针,数组指针可以看成是迭代器。int a[]{1,2,3};int *p=a; //p就是一个迭代器,迭代器都是和容器共同使用的,有些迭代器是和容器无关的。
算法:操作容器中的元素。术语操作、函数、算法可以相互替换。查找、排序、比较、替换。

STL容器

容器类

在这里插入图片描述
所有容器的共同函数->
在这里插入图片描述
一级容器的通用函数
在这里插入图片描述
顺序容器:vector、list、queue、stack
关联容器:set、map
set时使用的insert函数
vector的输出数据和输入数据相同
set的输出数据和输出数据不同
迭代器可以分为5类
input(可读取) iterators/output(可修改) iterators通过迭代器只读/只写容器内容
Forward iterators/Bidirectional iterators/Random access iterators迭代器的变化方向:单步、向前、向后、随机跳转
支持随机访问、元素严格有序(类似数组):vector、deque
仅能通过双向迭代器(类似链表):list、set、map
没有迭代器:stack、queue、priority_queue
迭代器支持的运算符:
在这里插入图片描述
vector和deque是数组实现的
list是链表实现的
vector、deque、list的函数
在这里插入图片描述
迭代器进入函数中,有可能失效
list调用insert函数中的迭代器,迭代器指向的数据不变,比如说list v{1,2,3};
list::iterator iter=v.begin();
v.insert(iter.2);//操作完之后,iter指向的还是1
list1.merge(list2);//将list2连接到list1后面,清空list2
list1.reverse();//反转
list1.sort();//从小到大排序
list1.remove(1);//删掉list1中所有的1
list list1(list2.begin(),list2.end());
list1.splice(p,list2);//将list2中的元素按某种方式进行切分,放到list1的p后面

关联容器

按照键值快速存取元素;元素按规则排序;默认是按照<运算符排序
set不能重复重复
map存储键值对,不能重复
在这里插入图片描述
multiset set1(value,value+6);//默认升序排序
multiset<int,greater > set2(value,value+6);//按照降序方式排序

lower_bound();//返回指向当前元素的迭代器
uppper_bound();//返回指向元素后面的迭代器
map1/set1.find(key);//返回当前元素的迭代器

set1.cout(2);//返回2元素的数量

map<int,string> map1;//map1中key是整型,value是string
map1.erase(103);//删掉指定键所对应的键值对
map1.insert(map<int,string>::value_type(100,”xiaowang”));

p->frist//返回key
p->second//返回value

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值