c++基础部分
变量和基本类型
复合类型
指针
指针基本操作
指针一定要进行初始化,防止对野指针操作
//得到空指针
int *p1 = nullptr; // 这是一种字面值
int *p2 = 0; //也是一种空指针定义方式,等价于 *p2 = NULL
如果指针为 0,(nullptr) ,则在相当于 bool 的false,非 0(nullptr) 的都是 true
用 == 或 != 判断两个指针,如果两个指针中存的地址相同,则相等
void 指针*
void* 指针可以指向任何对象的地址,不能直接操作 void* 指向的对象(因为不清楚该指向对象的类型)
** 指向指针的引用**
int a = 10;
int *p1 = 0;
// 对指针的引用( p2 是对指针 p1 的引用)
int *&p2 = p1;
// 因为 p2 是对 *p1 的引用,相当于 a 的地址给了 p1
p2 = &a;
cout << *p2 << endl;
上面代码中,*&p2
想要理解该变量的类型,最简答的方法是 **从右向左 ** 阅读,离变量名最近的符号对变量的类型影响最大, *&p2
表示是一种引用,是对指针的引用,int *&p2
表示是对 int 类型指针的引用
左值、右值、左值引用和右值引用
左值:lvalue(locator value)存储在内存中,有明确内存地址的数据(可寻址)
右值:rvalue(read value)可以提供数据值的数据
可简单理解为等号左边为左值,等号右边为右值,左值可以当做右值使用
int a = 10;// a 为左值
int b = a; // 左值当做右值使用
上述例子中的 a 和 b 都能使用 & 取出地址,但 10 不能取出地址
左引用:用&表示
右引用: 用 && 表示,右引用也必须初始化
int a = 10;
int &b = a; // b 为左引用
int &c = 10; // 错误
int &&d = 10;
const限定符
const 限定符限定的变量必须进行初始化,初始化后值则不能改变
const 限定的变量仅在当前文件中有用,如果在不同的文件中出现了同名 const 变量时,相当于定义了多个独立的变量,如果想让 const 变量在多个文件中共享,需要加上 extern
extern详解链接。
// 仅在该文件中有效,其他文件无法访问到
const int a = 0;
// 在其他文件中也能访问到
/*
extern 表示声明一个变量,定义该变量在其他地方
*/
extern const int b;
const 引用
对 const 变量的引用也需要加上 const
const int a = 10;
const int &b = a;
const 常量是对引用的操作做出了限制,
int a = 10;
// 可以使用常量整形引用去引用证信息整形,但时无法使用 b 引用去修改 a 的值(相当于对操作做出了限制)
const int &b =a;
const double x = 1.0;
const double *ptr = &x;
cout << *ptr << endl;
double y = 1.0;
const double *ptr2 = &y;
// 产生错误,因为 *ptr2 无法修改
//*ptr2 = 2.0;
常量指针与指针常量
常量指针
指针本身为常量,其中存储的地址无法改变,即指向的地址不变
int num = 10;
// 常量指针(首先是常量,其次才是指针)
int *const ptr = #
int *const ptr
离 ptr 最近的是 const ,表示 ptr 本身是一个常量,然后是 * 表示是一个常量指针( * 和 const 都是一种声明符)
指针常量
指针指向的内容为常量,表示不可修改指针指向的变量内容
int num2 = 20;
// 指针常量(首先是指针,其次是常量)
const int *ptr2 = #
顶层const 和底层const
顶层 const :指针本身是一个常量
底层 const: 指针指向的对象是一个常量
默认初始化
变量默认初始化与变量的类型和定义位置有关,
内置类型的默认初始化由变量定义位置决定,定义在函数体外的变量被初始化为 0 , 定义在函数体内部的内置类型不被默认初始化
constexpr 与常量表达式
常量表达式:值不会改变,且在编译阶段就能得到计算结果的表达式。字面值属于常量表达式
变量使用 constexpr 声明,编译器会来验证变量的值是否是一个常量表达式
constexpr int a = 20; // 20 是常量表达式(因为字面值属于常量表达式)
constexpr int b = a+1; // a + 1 是常量表达式
算数类型、指针、引用都属于字面值类型,自定义的类、IO库、String 类型不属于字面值
constexpr 修饰的指针即可以指向常量,也可以指向非常量
// constexpr 声明的指针仅对该指针有效,对该指针指向的变量无关,即与常量指针类似
int a = 0;
constexpr int *ptr3 = &a;
类型别名
类型别名:给类型换一个名字,让类型名称更容易理解
常见的为 typedef
,c++11 中可以使用 using
typedef
typedef 类型 类型别名;
// 用 wage 代替 double* 类型
double num3 = 10.;
typedef double* wage;
wage num = &num3;
using
using 类型别名 = 类型;
using wage = double*;
double num3 = 10.;
wage num4 = &num3;
auto
auto类型: 让编译器通过初始值来推断类型
自定义数据类型
预处理器概述
防止头文件重复导入
#define 把一个名字设置为预处理变量
#ifdef 当且仅当变量定义时为真。
#ifndef 当且仅当变量未定义时为真,一旦为真,则执行后续操作,知道遇到 #endif 指令为止。
字符串、向量和数组
string
string 表示可变长的字符序列,使用string类型必须首先包含 string 头文件 include<string>
string 常见初始化方式
string s1;
string s2 = s1;
string s3(s1);
string s4 = "enen";
string s5("enen"); // 等价与 string s5 = "enen";
string s6(10, 'e'); // 十个字符 e
拷贝初始化与直接初始化
拷贝初始化:直接使用等号初始化一个变量
// 拷贝初始化
string s4 = "enen";
// 直接初始化
string s5("enen");
string s5("enen");
// s5.empty() 如果 s5 为空,则返回 false
if(!s5.empty()){
cout << s5.empty() << endl;
}
// .size() 返回字符串的长度,返回值类型为 string::size_type
string::size_type length = s5.size();
cout << length << endl;
string 类型中的比较都是按字符进行比较
string对象、字符字面值、字符串字面值混用是,必须保证加号 + 两侧有一个是 string 对象
string s6(10, 'e');
string s7 = s6 + ",";
string s8 = s6 + "," + "enen"; // 等价与 (s6 +",") + "enen"
cout << s8 << endl;
遍历 string 中的每个字符,并改变字符
string s = "enen";
for (auto c : s) {
cout << c << endl;
}
for (auto& c : s) {
c = toupper(c); // 把 c 变成大写,对 c 的所有操作,都会改变原来的字符串
}
cout << s << endl;
Vector
// vector 中嵌套 vector ,vector<vector<int>\空格>
vector<vector<int> >
vector声明和初始化与string类似
vector<int> vec1;
vector<int> vec2(10); /// 初始化,设定元素数量,会使用默认值
vector<int> vec3 = { 1,2 }; // 列表初始化
vector<int> vec4{ 1,2,3 };
vector中添加元素
vector<int> vec;
for (size_t i = 0; i < 1000; i++)
{
vec.push_back(i);
}
// 获得 vector 大小
vector<int>::size_type length = vec.size();
if (!vec.empty()) {
cout << length << endl;
}
vector和string类型相同,下标类型都是 size_type 类型,只能对已知存在的元素执行下标操作
迭代器
begin
返回指向首元素的迭代器
end
返回指向尾元素的下一个位置的迭代器
当容器为空时,begin
和 end
指向的都同一个迭代器,都是尾后迭代器
迭代器常见操作
*iter | 返回迭代器 iter 所指元素的引用 |
---|---|
iter->mem | 解引用 iter并获取该元素名为 mem 的成员,等价于 (*iter).mem |
++iter | 令 iter 指向容器的下一个元素 |
–iter | 令 iter 指向容器的下一个元素 |
iter1 == iter2 iter1 != iter2 | 判断两个迭代器是否相等 |
string str = "hello world";
for (auto it = str.begin(); it != str.end(); it++)
{
(*it) = toupper(*it);
}
cout << str << endl;
迭代器进行 + -运算,相当于向前或者向后移动,iter1 - iter 2 的类型为 difference_type
是一种带符号的整型数,
进行 >=、 <= 、> 、< 相当于判断两个迭代器的位置
数组
数组中的元素会被默认初始化的
int a[10];
int b[] = { 1,2,3 };
数组初始化中不能拷贝其他数组的内容作为初始化内容,
int a[] = {1,2};
// 错误用法
int b[] = a;
数组的阅读方式数组名开始,从内向外阅读
// 是一个整型指针的数组
int *ptrs[10];
// 是一个指针,指向 10 个 int 类型的数组
int(*parray)[10];
//是一个引用,对整型指针数组的引用
int *(&parray2)[10] = ptrs;
数组下标和 vector 、string 相同,都是 size_t 类型
在使用到数组名的地方,编译器会自动将其替换为指向数组首元素的指针
string nums[] = { "one","two","three" };
//等价于 string *p = &nums[0];
string *p2 = nums;
标准库函数:begin和end
string nums[] = { "one","two","three" };
// 获得指向数组首元素的指针
string *pBegin = begin(nums);
// 获得指向数组尾元素下一个元素的指针
string *pEnd = end(nums);
for (auto it = pBegin; it != pEnd; it++)
{
cout << *it << endl;
}
指针相减的数据类型为 ptrdiff_t
是一种带符号的类型,仅当指针指向相同对象,比较才有意义
ptrdiff_t p = pBegin - pEnd;
指针与下标的关系
string nums[] = { "one","two","three" };
string a = nums[2];
string *ptr = nums;
string b = *(ptr + 2);
多维数组
// a 有个三个元素,每个元素又是一个含有 4 个元素的数组,相当于是一个 三行四列 的数组
int a[3][4];
int b[3][4] = {
{1,1,1,1} ,
{2,2,2,2},
{3,3,3,3}
};
int c[3][4] = {1,1,1,1,2,2,2,2,3,3,3,3};
for遍历多维数组注意事项
数组名在编译时会自动转换成指针,在下面例子中,如果const auto &row : a
没有引用,写成 const auto row : a
,则 row 会被解析成 int *
,指针无法遍历,因此必须加上引用,如果不想修改值,则可以加上 const
要是用范围 for 语句处理多为数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型
int a[3][4] = {
{1,1,1,1} ,
{2,2,2,2},
{3,3,3,3}
};
for (const auto &row : a) {
for (const auto &col : row) {
cout << col << endl;
}
}
表达式
运算符能后重载,但是运算对象的个数、运算符优先级和结合律是无法改变的
*ptr++
相当于先解引用,再自增
成员访问运算符 ->
, ptr->men
相当于 (*ptr).men
,解引用运算符低于点运算法,因此要在 *ptr
加上括号
sizeof 运算符
sizeof 返回一条表达式或者一个类型名字所占的字节数,sizeof 满足右结合律,返回值是一个 size_t 类型
使用方式: sizeof(type)
或者 sizeof type
string str = "a string";
size_t length = sizeof(str);
cout << length << endl;
通过 sizeof 求数组长度
int a[10] = { 1,2,3 };
int num = sizeof(a) / sizeof(a[0]);
cout << num << endl;
类型转换
c++不会直接将两个不同类型的值进行相加
当表达式中既有整数类型,又有浮点数类型时,会自动将整数类型转换成浮点数类型。
函数
函数声明
函数只需要声明一次,但是可以定义多次
函数三要素:返回值、函数名、形参类型。
应该在头文件中对函数进行声明
分离式编程
分离式编程:可以将程序分割到几个文件中去,每个文件单独编译
参数传递
传值参数
指针形参:会将传递的指针进行拷贝,两个指针地址并不相同,但指向的地址相同,所以可以通过指针修改指向的值
传引用参数:会改变传入参数的值,
使用引用传参好处:1、使用引用传参可以避免拷贝大的类型对象
2、可以返回额外的信息
参数传递的规则和变量初始化一样,因为在函数中也是创造一个形参变量,然后将实参值赋值给形参
#include <iostream>
#include<string>
using namespace std;
void printString(const string &str);
int main()
{
// "hello world" 是 const char[] 类型,并非是 string 类型
printString("hello world");
string a;
cin >> a;
//std::cout << "Hello World!\n";
}
void printString(const string &str) {
cout << str << endl;
}
数组参数
数组的两个特性:1、数组不允许拷贝。2、在使用到数组名的地方,编译器会自动将其替换为指向数组首元素的指针
因为数组的特殊性,在传递数组时,实际上是传递指向数组的指针
在传递数组时,除非要对数组进行写操作, 一般都加上 const
修饰符
void printInfo(const int*)
管理指针新参的三种方法:
1、使用数组标记数组长度
要求数组本身包含一个结束标记为,例如string类型中空字符
void print(const char *cp){
if(cp){// 数组不为空指针
while(*cp){
cout<<*cp++;
}
}
}
2、使用标准库规范
传递数组的首元素和尾后元素指针
void print(const int *pBegin,const int *pEnd){
if(pBegin != pEnd){
cout<<*p++<<endl;
}
}
3、显示传递一个表示数组大小的形参
void print(const int ia[],size_t size){
for(size_t i = 0;i<size;i++){
cout<<ia[i]<<endl;
}
}
数组引用形参
int (&arr)[10] // 对长度为 10 的数组的引用
int &arr[10] // 从右往左度, arr 和 [] 靠的更近,是一个数组,数组中的内容都是引用
含有可变形参的函数
1、函数实参数量未知,但全部实参的类型都相同。可以使用 initializer_list 类型的形参
2、如果实参类型不同,可以使用可变参数模板
initializer_list
是一种标准库类型,用于表示某种特定类型值的数组(本质是数组)
initializer_list中的元素永远是常量值,无法修改其元素值
void error_msg(initializer_list<string> il) {
/* initializer_list 提供了 .begin() 指向首元素
.end() 指向尾后元素
.size() 表示类别元素数量
*/
for (auto p = il.begin(); p != il.end(); p++)
{
cout << *p << endl;
}
}
int main()
{
error_msg({ "one","tow","three" });
}
函数返回
函数的返回值和对形参赋值一样,都是创建一个临时变量,然后进行赋值
不要返回局部对象的引用或指针:函数调用结束后,局部变量会被释放,如果返回引用或指针,则表示引用或指针指向的不是有效区域
返回值是引用类型,则返回左值
列表初始化返回值
vector<string> process() {
if (exceptd.empty()) return {}; // 返回空的 vector
else if (excepted == actual) return { "functionX","okay" }; // 返回列表初始化的 vector 对象
}
主函数 main 如果返回值为 void ,函数体中没有 return,编译器将默认加上 return
函数重载
函数重载:函数名相同,但形参不同(类型或数量不同)
有时候两个新参看起来不一样,但实际上是相同的
int lookup(const string &acct);
int lookup(const string &); // 省略了形参,其实和上面函数相同
顶层 const 不影响传入函数的对象,一个有顶层 const 的形参函数没法和没有顶层 const 的形参函数区分开
(顶层const 表示本身就是个常量)
// 下面两个相同
int lookup(const int a);
int lookup(int a);
如果形参是指针或引用,则要看是常量对象,还是非常量对象
int lookup(int *p);
int lookup(const int *P); // 发生了函数重载,指针常量,是底层const,指针指向的值不能改变
int lookup(int *const p); // 没有发生重载,本身是个常量,顶层 const ,
在内层作用于中声明名字,将隐藏外层作用于中声明的同名实体,在不同的作用于中无法重载函数名
在内层作用域中可以对外层作用域重新定义
注意:重新定义是定义了一个新的只属于这个内层作用域的变量,原本的那个外层作用域中的变量未受到影响,这个新定义的内层作用域里的变量只能在这个内层作用域中使用。
void info(int );
void info(double);
void print() {
void info(const string &);
int a = 10;
info(a); // 报错,不存在从"int" 转换到"std::basic_string<char,std::char_traits<char>,std::allocator<char>>"的适当构造函数
}
特殊用途语言特性
默认实参
调用含默认实参的函数,可以对默认实参赋值,也可以不赋值(使用默认实参)
有默认值的形参要放在形参列表的最后,
在给定的作用于中,一个形参只能被赋予一次默认实参,函数的后续声明只能为之前那些没有默认值的形参添加默认值
string screen(sz,sz,char=' ');
string screen(sz,sz,char='*'); // 错误,重复声明,改变了默认实参的值
string screen(sz = 24,sz=80,char);//正确,添加默认实参
局部变量不能为默认实参赋值
内联函数和constexpr函数
内联函数:可避免函数调用时的系统开销
内联函数适用于规模小、流程直接、频繁调用的函数
inline const string &shorterString(const string &s1,const string &s2){
return s1.size() <= s2.size() ? s1:s2;
}
cout << shorterString(s1,s2) << endl;
// 在编译过程,会将内敛函数编译成这样
// cout << ( s1.size() <= s2.size() ? s1:s2 ) << endl;
constexpr 函数:函数返回类型以及所有的形参类型都是字面值类型,函数体重有且仅有一条 return 语句
constexpr函数会被编译器隐式转换成内联函数(inline)
constexpr int new_sz(){return 42;}
constexpr int foo = new_sz(); // 正确,foo是一个常量表达式
内联函数和constexpr函数要放在头文件中,因为此类函数定义必须完全一致
函数匹配
1、精准匹配
- 实参类型和形参类型相同
- 实参从数组类型或函数类型转换成对应的指针类型
- 向实参添加顶层 const 或者从实参中删除顶层 const
2、通过 const 转换实现的匹配
3、通过类型提升实现的匹配
4、通过算数类型转换或指针转换实现的匹配
5、通过类类型转换实现的匹配
函数指针
函数指针指向的是函数而非对象,指向某种特定类型(函数类型由他的返回类型和形参类型共同决定,与函数名无关)
声明函数指针,只需要用指针替换函数名即可
bool lengthCompare(const string & ,cosnt string &);
// 指针的括号一定要加
bool (*pf)(const string & ,const string &); // 函数指针,未初始化
bool *pf(const string & ,const string &); // 声明一个函数名为 pf 的函数,返回类型为 bool 指针
// 函数指针指向函数,函数类型和函数指针类型一定要匹配
pf = lengthCompare; // 函数名赋值给指针,会自动转换, & 可加可不加
pf = &lengthCompare; // 等同于上面的
// 可通过函数指针方式调用函数
pf("hello","world");
(*pf)("hello","world"); // 与上面等价
函数有重载时,函数指针必须与某个重载函数类型相同
函数指针当做形参
void userBigger(const string &s1,const string &s2,
bool pf(const string &,const string &));
// 与上面等价
void userBigger(const string &s1,const string &s2,
bool (*pf)(const string &,const string &))
userBigger("hello","world",pf);
// 可以对 bool (*pf)(const string &,const string &) 取别名
typedef decltype(lengthCompare) *FuncP;
void userBigger(const string &s1,const string &s2,
FuncP)
函数返回类型为函数指针
// 需要将函数返回类型写成指针形式
using F = int(int *,int); // 则PF是函数类型,不是指针
using PF = int(*)(int *,int); // PF 为函数指针,如果写成 using PF = int(int *,int);
F *f1(int);
PF f1(int);
类
定义抽象函数
常量成员函数:非静态成员函数后加上 const,表示成员函数隐含传入的 this 指针为 const 指针,决定了在该成员函数中,任意修改它所在的类成员操作都是不允许的(因为隐含了对 this 指针的 const 引用)
类作用域和成员函数
编译器是分两步处理类:首先编译成员的声明,在编译成员函数体,因此,成员函数体可以随意使用类中的其他成员,无须在意这些成员出现的顺序。
类外部定义成员函数
需要函数签名保持一致,并在函数名前面加上类名
doubel Sales_data::avg_price() const{
if(units_sold)
return revenue/units_sold;
else
return 0;
}
Sales_data& Sales_data::combine(const Sales_data &rhs){
units_sold +=rhs.units_sold;
revenue += rhs.revenue;
return *this; // 返回调用该函数的对象
}
构造函数初始化列表
// 为成员赋初值
// bookNo 初值为 s ,units_sold 初值为 n ,revenue 初值为 p*n
// 一般函数体都是空的,因为构造函数的初始化列表就是给成员变量赋初值
Sales_data(const std::string &s,unsigned n,double p):
bookNo(s),units_sold(n),revenue(p*n){}
访问控制与封装
class 默认访问等级为 private ,struct 默认访问等级为 public ,用 class 或 struct 定义类的唯一区别就是默认访问等级不一样
友元
类可以允许其他类或这函数访问它的 private 成员,在类中以 friend 关键字开始的函数声明语句
友元声明仅仅只是指定了访问权限
class Sales_data{
friend Sales_data add(const Sales_data &,const Sales_data &); // 表示 add 函数能后访问 Sales_data 中的 private 成员
public:
Sales_data = defualt;// 表示默认构造函数
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue - 0.0;
}
Sales_data add(const Sales_data &,const Sales_data &);
类的其他特性
内联函数
#include<string>
class Screen {
public:
typedef std::string::size_type pos;
Screen() = default; // 因为已有自定义构造函数,所以默认构造函数时必须的
Screen(pos ht, pos wd, char c) :height(ht), width(wd), contents(ht*wd,c) {};
char get() const { // 读取光标位置,隐式内联
return contents[cursor];
}
inline char get(pos ht, pos wd)const; // 显示内联
Screen &move(pos r, pos c); // 能在之后被声明为内联函数
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
// 在类外声明成内联函数
inline Screen &Screen::move(pos r, pos c) {
pos row = r * width;
cursor = row + c;
return *this;
}
// 之前在类内中已经声明成了内联函数了
char Screen::get(pos r, pos c) const {
pos row = r * width;
return contents[row + c];
}
可变数据成员
用 mutable 修饰的成员变量,表示可以更改成员变量的值,在 const 成员函数中也能改变该值
class Screen{
public:
void some_member() const;
private:
mutable size_t access_ctr;
}
void Screen::some_member() const{
++access_ctr; // 该变量使用 mutable 修饰,在 const 函数中也能改变
}
类数据成员的初始值:在声明时直接进行赋值
**返回 this 的成员函数:返回的是当前对象的克隆或者本身
如果返回 this ,则返回的是当前对象的地址
如果成员函数使用 const 修饰,则返回 *this 也是 一个 const 对象
可以通过成员函数是否是 const 来对成员函数进行重载
class Sales{
private:
string content;
public:
void display()
}
const 重载
声明:<类型标志服> 函数名 (参数表) const;
说明:
-
const 是函数类型的一部分,在实现部分也要带该关键字
-
const 关键字可以用于对重载函数的区分
-
常量成员函数不能更新类的成员变量,也不能调用该类中没有用 const 修饰的成员函数,只能调用常量成员函数
-
非常量对象也可以调用常量成员函数,但是如果有重载的非常量成员函数,则会有限调用非常量成员函数
类类型
类的前向声明: 将类的声明和定义分开,先声明类,此时类是不完全类型
类先声明,然后类才允许包含指向自身类型的引用或指针。
class Link_Screen{
Screen window;
Link_Screen *next;
Link_Screen &prev;
}
友元再探
可以将另外一个类或者另一个类的成员函数设置成友元,则另一个类或者另一个类的成员函数可以访问该类中的所有成员。
如果函数发生了重载,要将所有的函数重载版本都设置成友元才可以,
在定义友元函数之前,该函数或类,必须先声明过,
构造函数再探
构造函数的初始化列表和在构造函数中赋值,
可以先定义类,再对变量赋值,但如果成员是 const 或 引用 类型,必须使用初始化列表对其初始化
类的成员变量初始化顺序由定义顺序决定
委托构造函数
将构造任务交给其他构造函数
class Sales_data {
public:
// 非委托构造函数
Sales_data(std::string s, unsigned cnt, double price) :bookNo(s), units_sole(cnt), revenue(cnt*price) {}
// 委托构造函数,将初始化任务交个其他构造函数
Sales_data() :Sales_data("", 0, 0) {}
Sales_data(std::string s) :Sales_data(s, 0, 0) {}
private:
std::string bookNo;
unsigned units_sole;
double revenue;
};
使用默认构造函数
Sales_data obj; // 使用默认构造函数创建类
explicit:抑制构造函数隐式类型转换
class Test1 {
public:
Test1(int n) :num(n) {};
private:
int num;
};
class Test2 {
public:
explicit Test2(int n) :num(n) {};
private:
int num;
};
void tes() {
Test1 t1 = 10; // 使用了类型转换
Test2 t2(10); // 使用构造函数构造类
Test2 t22 = 20; // 拷贝构造形式 报错,不存在从"int"转换到"Test2"的适当的构造函数
}
类的静态成员
使用作用于运算符 :: ,类名::静态成员 来访问
静态成员在类内使用 static 声明,可定义在类内,也可以定义在类外(类外则不需要加 static )
c++标准库
IO库
IO 类
头文件 | 类型 | 作用 |
---|---|---|
iostream | istream , wistream | 从流读取数据 |
ostream , wostream | 向流写入数据 | |
iostream , wiostream | 读写流 | |
fstream | ifstream , wifstream | 从文件读取数据 |
ofstream , wofstream | 向文件写入数据 | |
iofstream , wiofstream | 读写文件 | |
sstream | istringstream , wistringstream | 从 string 读取数据 |
ostringstream , wostringstream | 向 string 写入数据 | |
iostringstream , wiostringstream | 读写 string |
不能对 IO 对象拷贝或者赋值,因此函数形参和返回值都不能设置为 流 类型,对 IO 操作的函数,需要使用引用的方式操作 流,同时传递的引用不能是 const ,
流只有处于无错误状态才能够进行读写操作,因此在读写前应加入判断,确定流状态最简单的方法是当做条件使用
while(cin>>word){
// 读取成功后的操作
// ...
}
查询流状态: badbit() 不可恢复的错误,badbit被置位,流就不可用 ,failbit 可恢复错误
如果达到文件结束为 efobit() failbit() 会被置位
管理条件状态
流对象的 rdstate 成员返回一个 iostate 值,对应流的当前状态
setstate 对给定条件置位,表示发生了对应的错误,
clear 有两个版本,无参版表示清除所有错误标志位,执行 clear() 后,调用 good 会返回 true
auto old_state = cin.rdstate(); // 获得 cin 当前状态
cin.clear(); // 使 cin 有效,(发生错误也清除变得有效)
有参版的 clear 接受一个 iostate 值,表示流的新状态,可以清除特定错误
//清除 failbit 和 badbit 的错误信息,
// cin.failbit 对应的操作位被置位,进行取反,然后将 cin 的对应状态变为 0,则清除了错误信息
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit)
管理输出缓冲
cerr 会立即刷新缓冲区,
endl 刷新缓冲区,并换行,再刷新缓冲区
flush 刷新缓冲区
ends 刷新缓冲区,然后向缓冲区插入一个空字符,然后再刷新缓冲区
让一段代码中每次操作后都刷新缓冲区,可以使用 unitbuf ,让接下来的每次读写操作之后都进行一次 flush 操作, nounitbuf 重置流,使其恢复正常
cout << unitbuf; // 何输出都会被刷新,无缓冲
// 任何输出都会被刷新,无缓冲
cout << nounitbuf; // 回到正常刷新方式
文件输入输出
fstream 操作
// 构建方式
fstream fstrm; // 创建一个未绑定的文件流
fstream fstrm(s); // 创建一个 fstream ,并打开名为 s 的文件
fstream fstrm(s,mode); // 创建一个 fstrm,并按照 mode 的方式打开文件
fstrm.open(s); // 打开名为 s 的文件,并将文件与 fstrm 绑定
fstrm.close(); // 关闭 fstrm 绑定的文件,返回 void
fstrm.is_open() // 返回一个 bool 值,指出与 fstrm 关联的文件是否成功打开
在要求使用基类对象的地方,可以使用继承类来代替,意味着接受一个 iostream 类型的引用参数的函数,可以用 fstream 类型来调用
ofstream out; // 创建流
out.open(fileName + ".txt"); // 与文件关联,打开文件
if(out){ // 如果文件打开,则执行 if 操作
// ...
}
当 fstream 对象被销毁时, close 会自动被调用
文件模式
in 以读方式打开,只可以对 ifstream 或 fstream 对象设定
out 以写方式打开,只可以对 ofstream 或 fstream 对象设定,以 out 模式打开,文件内容会被丢弃,因此要配合 app 使用
app 每次写操作前均定位到文件末尾,只要 trunc 没有设定,则可设定 app 模式,
ate 打开文件后立即定位到文件末尾
trunc 截断文件
binary 以二进制方式进行 IO
ate 与 binary 可以用于任何类型的文件流对象,且可以与其他任何文件模式组合使用
ofstream out;
out.open(fileName); // 输出为截断模式,会清除文件原有内容
out.close(); // 关闭文件,以便该流操作其他文件
out.open(fileName,ofstream::app); // 以追加的方式打开文件
out.close();
string 流
istringstream 从 string 读取数据
ostringstream 向 string 写入数据
iostringstream 读写 string
sstream strm; // strm 是一个绑定 stringstream 对象
sstream strm(s); // strm 是一个 sstream 对象,保存 string s 的一个拷贝,此构造函数时 explicit (禁止类型隐式转换)
strm.str() // 返回 strm 所保存的 string 的拷贝
strm.str(s) // 将 string s 拷贝到 strm 中,返回 void
struct PersonInfo
{
string name;
vector<string> phones;
};
int main() {
string line, word;
vector<PersonInfo> people;
while (getline(cin,line)) // 输入 morgan 2015552368 8625550123
{
PersonInfo info;
std::istringstream record(line);
record >> info.name; // 输出 morgan 到 info.name 中
while (record >> word) {
info.phones.push_back(word); // 轮流输出 2015552368 8625550123
}
people.push_back(info);
}
}
顺序容器
常见顺序容器如下
vector 可变大小数组,支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
deque 双端队列。支持快速随机访问,在头尾位置插入/删除速度很快
list 双向链表。只支持双向顺序访问。在 list 中任何位置进行插入/删除操作速度都很快
forward_list 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快
array 固定大小数组。支持快速随机访问。不能添加或删除元素
string 与 vector 相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除 速度快
vector 与 string 访问快, list 和 forward_list 是插入/删除快
一般默认选择 vector ,除非有着明确的需求
如果不确定使用哪种容器,可以在程序中只是用 vector 和 list 的公共操作:使用迭代器,不适用下标操作,避免随机访问。这样在必要时在 vector 和 list 之间切换很方便
容器操作表
下述容器操作是所有容器都支持的操作(除非特别指出不支持)
类型别名
iterator 此容器类型的迭代器类型
const_iteratro 可读取元素,但不能够修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型,(实际就是传入模板的类型), vector
::value_type n1(520); //等价于 p n1(520);
reference 元素的左值类型,与 value_type& 含义相同
const_reference 元素的 const 左值类型(即:const value_type& )
构造函数
C c 默认构造函数,构造空容器
C c1(c2) 构造 c2 的拷贝 c1
C c(b,e) 构造 c,将迭代器 b 和 e 指定范围内的元素拷贝到 c (array 不支持)
C c{a,b,c…} 列表初始化
赋值与 swap
c1 = c2 将 c1 中的元素替换为 c2 中元素
c1 = {a,b,c…} 将 c1 中的元素替换为列表中的元素(array不支持)
a.swap(b) 交换 a 和 b 的元素
swap(a,b) 交换 a 和 b 的元素
大小
c.size() c 中元素的数目( forward_list 不支持)
c.max_size() c 可保存的最大元素数目
c.empty() 若c 中存储了元素,返回 false,为空则返回 true
添加/删除元素( array 不适用)
c.erase(args) 删除 args 指定的元素
c.clear() 删除 c 中的所有元素
获取迭代器
c.begin(), c.end() 返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(),c.cend() 返回const_iterator (当不需要 写 操作时,可使用)
反向容器的额外成员(不支持forward_list)
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(),c.rend() 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(),c.crend() 返回const_reverse_iterator
C c(n,t) n 个初始化值为 t 的元素 ,该构造方式是顺序容器所独有的
容器库概览
array 容器
// 创建 array ,必须指定 数据类型 和 容器大小
array<int,10> // 类型为: 保存 10 个 int 的数组
array<int,10>::size_type i; // 数组类型包括元素类型和大小
//与内置数组不同,标准库array类型允许赋值。赋值号左右两边的运算对象必须具有相同的类型:
array<int,10>a1={0,1,2,3,4,5,6,7,8,9};
array<int,10>a2={0}; //所有元素值均为0
al=a2; //替换al中的元素
a2={0}; //错误:不能将一个花括号列表赋予数组
赋值操作
assign 操作不适用于关联容器和 array,仅使用于顺序容器
seq.assign(b,e) 将 seq 中的元素替换为选代器 b 和 e 所表示的范围中的元素。迭代器 b 和 e 不能指向 seq 中的元素
seq.assign(il) 将 seg 中的元素替换为初始化列表 i1 中的元素
seq.assign(n,t) 将 seq 中的元素替换为 n 个值为t的元素
swap操作
swap 交换两个相同容器的内容,元素本身并未交换,swap只是交换了两个容器的内部数据结构。
元素不会被移动的事实意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。但是,在swap之后,这些元素已经属于不同的容器了。例如,假定iter在swap之前指向svecl[3]的string,那么在swap之后它指向svec2[3]的元素。与其他容器不同,对一个string调用swap会导致迭代器、引用和指针失效。
关系运算符
所有容器都支持相等运算符(== 、 !=) 除了无需关联容器外,所有容器都支持关系运算符(> 、<、 >= 、<=)
先比较容器中每个元素的大小,之后再比较容器中的元素个数
顺序容器操作
顺序容器中所独有的操作
这些操作会改变容器的大小;array不支持这些操作。
forward_1ist有自己专有版本的insert和emplace;forward_list不支持push_back和emplace_back。
vector和string不支持push_front和emplace_front。
c.push_back(t) 在c的尾部创建一个值为t或由args创建的元素。返回
voidc.emplace_back(args)
c.push_front(t) 在c的头部创建一个值为t或由args创建的元素。返回voidc.
emplace_front(args)
c.insert(p,t) 在迭代器 p 指向的元素之前创建一个值为 t 或由 args 创建的元素
c.emplace (p,args) 返回指向新添加的元素的迭代器
c.insert(p,n,t) 在迭代器p指向的元素之前插入 n 个值为t的元素。返回指向新添加的第一个元素的迭代器;若 n 为 0,则返回 p
c.insert(p,b,e) 将迭代器 b 和 e 指定的范围内的元素插入到迭代器 p 指向的元素之前。b 和 e 不能指向 c 中的元素。返回指向新添加的第一个元素的迭代器;若范围为空,则返回 p
c.insert(p,il) i1 是一个花括号包围的元素值列表。将这些给定值插入到迭代器p指向的元素之前。返回指向新添加的第一个元素的迭代器;若列表为空,则返回 p
访问元素
c.front() 返回首元素
c.back() 返回尾元素
c[n] 返回c中下标为n的元素的引I用,n是一个无符号整数。若n>=c.size(),则函数行为未定义
c.at(n) 返回下标为n的元素的引用。如果下标越界,则抛出一out_of_range异常
在使用 front() 和 back() 前先判断顺序容器是否为空,
vector<string> vec; // 空 vector
cout<< vec[0]; // 运行时错误,因为 vec 为空
cout<< vex.at[0]; // 抛出 out_of_range 异常
访问元素操作返回的都是该元素的引用,可以通过该引用改变容器的值
删除操作
这些操作会改变容器的大小,所以不适用于array。
forward_1ist有特殊版本的erase。forward_list不支持pop_back;
vector和string不支持pop_front。
c.pop_back() 删除c中尾元素。若c为空,则函数行为未定义。函数返回voidc.
pop_front() 删除c中首元素。若c为空,则函数行为未定义。函数返回voidc.
erase§ 删除迭代器p所指定的元素,返回一个指向被删元素之后元素的迭代器,若p指向尾元素,则返回尾后(off-the-end)迭代器。若p是尾后迭代器,则函数行为未定义
c.erase(b,e) 删除迭代器b和e所指定范围内的元素。返回一个指向最后一个被删元素之后元素的迭代器,若é本身就是尾后迭代器,则函数也返回尾后迭代器删除c中的所有元素。返回void
顺序容器大小调整
resize不适用于array
c.resize(n) 调整c的大小为n个元素。若n<c.size(),则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化
c.resize(n,t) 调整c的大小为n个元素。任何新添加的元素都初始化为值t
如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;对vector、string或deque进行resize可能导致迭代器、指针和引用失效
vector 和 string 容量管理
vector 和 string 都是提前分配空间,
c.capacity() 在不重新分配内存空间情况下,c 可以保存多少元素
c.reserve(n) 分配至少能容纳 n 个元素的内存空间
c.shrink_to_fit() 将 capacity() 减少为与 size() 相同大小
string 的额外操作
substr
string s("hello world");
string s2= s.substr(0,5); //s2=hel1o
string s3 = s.substr(6); //s3=world
string s4 =s.substr(6,11); //s3=world//抛出一个out_of_range异常
搜索字符串 find、rfind
string name("AnnaBelle");
auto posl = name.find("Anna"); // pos1 = 0 返回 A 的位置
auto pos2 = name.rfind("Bell"); // pos2 = 4
cout << posl << endl;
cout << pos2 << endl;
查找给定字符串中任何一个字符匹配的位置 find_first_of (第一次出现的位置)
查找不在给定字符串中的字符位置 find_first_not_of
string numbers("01234567"),name("r2d2");
auto pos = name.find_first_of(numbers);
string dept("023r34");
auto pos3 = name.find_first_not_of(dept);
数值转换
to_string (a) 将 a 转换成字符串
stod(a) 将字符串 a 转换成 double 类型 (string to double)
// 可以通过组合的方式,来获取字符串中的数值
string s2 = "pi = 3.1415926";
cout << stod(s2.substr(s2.find_first_of("+-.0123456789"))) << endl;
泛型算法
大多数算法都定义在 algorithm 头文件中,一小部分定义在 numeric 头文件中
find(起始位指针,指向尾后元素指针,要查找的值) 如果找到要查找的值,则返回指向该值的指针,如果未找到,则返回尾后元素指针
accumulate 包含在 numeric 头文件中,对范围内的元素进行求和
vector<int> vec1{ 1,2,3,4,5,6,7,8,9,10 };
vector<int> vec2{ 11,12,13,14,15 };
auto p = find(vec1.begin(), vec1.end(), 1);
cout << *p << endl;
int arr[] = { 1,2,3,4,5 };
int* result = find(begin(arr), end(arr), 4);
cout << *result << endl;
int sum = accumulate(begin(arr), end(arr), 0); // 最后一个参数决定了对什么类型值求和,以及函数的返回类型
cout << sum << endl;
vector<string> strVec{ "one","two","three" };
// 对字符串也可以相加(拼接)
// "enen" 是 const char* 类型,并非是 string 类型
auto strResult = accumulate(strVec.begin(), strVec.end(),string( "enen"));
cout << strResult << endl;
fill(开始指针,结束指针,需要填充的值) ,开始指针和结束指针应小于等于容器的范围
fill_n(开始指针,赋值的个数,赋值的值)
template<class T>
void printAllElement(T vec) {
for (auto iterator = vec.begin(); iterator != vec.end(); iterator++)
{
cout << *iterator << " ";
}
cout << endl;
}
void mian(){
vector<string> strVec{ "one","two","three","four" };
fill(strVec.begin(), strVec.end()-strVec.size()/2, "oo");
fill(strVec.begin(),strVec.size(),"two");
printAllElement(strVec);
}
当 fill_n 给的范围超过容器的范围,会产生错误,可使用 back_inserter (插入迭代器),back_inserter 会自动调用 back_push ,扩充容器,back_inserter 包含在 iterator 头文件中
vector<int> vec;
fill_n(back_inserter(vec), 10, 0);
printAllElement(vec);
拷贝算法
copy 接受三个参数,前两个表示要拷贝的范围,最后一个表示目的序列的起始位置
int a[] = { 1,2,3,4,5 };
// sizeof(a) = 20,表示整个数组大小, sizeof(*a) = 4 表示数组第一个元素的大小
int b[sizeof(a) / sizeof(*a)];
cout << sizeof(a) << " " << sizeof(*a) << endl;
copy(begin(a), end(a), begin(b));
重排元素算法
sort 和 unique
vector<int> a{ 1,4,2,5,2,4,9,8 };
sort(a.begin(), a.end()); // sort 接受两个参数,表示要排序的范围
// unique 将范围内相同元素只保留一个,其余的放到容器最后,并指向 第一个重复的元素
// 1 2 4 5 8 9 2 4 返回值指向 2 的指针
auto end_p = unique(a.begin(), a.end());
a.erase(end_p, a.end());
定制操作
向算法传递函数
例如希望自定义排序方式,可以往 sort 传递第三个参数,此参数是一个 谓词(predicate)
谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词(unarypredicate,意味着它们只接受单一参数)和二元谓词(binarypredicate,意味着它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。
template<class T>
void printAllElement(T vec) {
for (auto iterator = vec.begin(); iterator != vec.end(); iterator++)
{
cout << *iterator << " ";
}
cout << endl;
}
bool isShorter(const string& s1, const string& s2) {
return s1.size() < s2.size();
}
int main()
{
vector<string> str{ "11","44444","222","555555","3333" };
// 字符串长度从小到大排序
sort(str.begin(), str.end(), isShorter);
printAllElement(str);
}
lambda表达式
lambda表达式形式为:
/*
[capture list] 捕获列表,是lambda 所在函数中定义的局部变量的列表,通常为空
(parameter list) 传入的参数列表
return type 返回类型
{function body} 函数体
一个 lambda 表达式相当于一个匿名的内联函数
可以忽略参数类型和返回类型,但必须要有捕获列表和函数体
*/
[capture list] (parameter list) -> return type {function body}
auto f = [] {return 42;};
// lambda 表达式调用方式与普通函数类似,都使用函数调用符() 调用
cout << f() << endl;
lambda 表达式不能有默认形参,因此 lambda 表达式 形参和实参数目一定相等
如果 lambda 表达式函数体只有一个 return 语句,则返回类型编译器可推出,如果有多个,则返回 void
lambda表达式中的捕获列表
[]表示不捕获任何变量
auto function = ([]{
std::cout << "Hello World!" << std::endl;
}
);
function();
[var]表示值传递方式捕获变量var
int num = 100;
auto function = ([num]{
std::cout << num << std::endl;
}
);
function();
[=]表示值传递方式捕获所有父作用域的变量(包括this)
int index = 1;
int num = 100;
auto function = ([=]{
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);
function();
[&var]表示引用传递捕捉变量var
int num = 100;
auto function = ([&num]{
num = 1000;
std::cout << "num: " << num << std::endl;
}
);
function();
[&]表示引用传递方式捕捉所有父作用域的变量(包括this),使用引用捕获能改变变量的值
int index = 1;
int num = 100;
auto function = ([&]{
num = 1000;
index = 2;
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);
function();
[this]表示值传递方式捕捉当前的this指针
#include <iostream>
using namespace std;
class Lambda
{
public:
void sayHello() {
std::cout << "Hello" << std::endl;
};
void lambda() {
auto function = [this]{
this->sayHello();
};
}
function();
};
int main()
{
Lambda demo;
demo.lambda();
}
[=, &] 拷贝与引用混合
[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量。
int index = 1;
int num = 100;
auto function = ([=, &index, &num]{
num = 1000;
index = 2;
std::cout << "index: "<< index << ", "
<< "num: "<< num << std::endl;
}
);
function();
[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。
不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:
[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。
如果希望改变捕获的值,则需要加上 mutable修饰符
int num = 40;
auto f = [=]() mutable {
return num * 10;
};
cout << f() << endl;
lambda表达式指定返回类型
int num = 20;
auto f = [=]()->int {
if (num > 10) return 20;
else return 30;
};
cout << f() << endl;