c++primer笔记

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 = &num;

int *const ptr 离 ptr 最近的是 const ,表示 ptr 本身是一个常量,然后是 * 表示是一个常量指针( * 和 const 都是一种声明符)

指针常量

指针指向的内容为常量,表示不可修改指针指向的变量内容

int num2 = 20;
// 指针常量(首先是指针,其次是常量)
const int *ptr2 = &num;

顶层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 返回指向尾元素的下一个位置的迭代器

当容器为空时,beginend 指向的都同一个迭代器,都是尾后迭代器

迭代器常见操作

*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 类

头文件类型作用
iostreamistream , wistream从流读取数据
ostream , wostream向流写入数据
iostream , wiostream读写流
fstreamifstream , wifstream从文件读取数据
ofstream , wofstream向文件写入数据
iofstream , wiofstream读写文件
sstreamistringstream , 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<int10>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(05;	  //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;

再探迭代器

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值