C++ 知识点
输入与输出:(iostream)
#include <iostream> using namespace std; int main(){ cout << "hello" << endl; // endl: 结束当前行,并将与设备关联的缓冲区中的内容刷到设备中 int v1,v2; cin >> v1 >> v2; return 0; }
字面值:字面值常量的形式和值决定了他的数据类型。
十进制:int、long、long long中最小那个
八进制、十六进制:int、unsigned int、long、unsigned long、long long、unsigned long long中最小那个。
尽管整型字面值可以存在带符号数据类型中,但严格来说十进制不会是负数,如-42,负号并不在字面值之内,他的作用仅仅对字面值取负数而已
默认初始化:定义于任何函数体之外的变量被初始化为0,定义在函数体内的内置变量将不被初始化。
声明和定义:声明规定变量的类型和名字,而定义还申请存储空间,也可能会为变量赋一个初始值。(变量可以被声明多次,但只能定义一次)
extern int i ; // 声明 int j; // 定义 extern int i = 0; // 定义
引用:为对象起另外的名字,必须初始化,必须严格匹配类型
int a = 0; int &r = a; int &r1; // 必须初始化 double i = 3.14; int &r2 = i; // 类型不匹配
指针:
void* 指针:可用于存放任意对象的地址。但不能对其的指向的对象进行操作(不知道具体类型),仅仅是内存空间。
const:(仅在文件内有效)必须初始化,不能改变的值。编译器将在编译过程中把用到该常量变量的地方都替换成对应的值。
const int i = get_size(); const int j = 42; const int j; // 错误,必须初始化
const的引用:对常量的引用不能被用作修改它所绑定的对象。
const int ci = 1024; const int &r1 = ci; r1 = 42; // 错误,不能修改常量的引用 int &r2 = ci; // 错误,不能让一个非常量引用指向一个常量对象
指针和const:
const double pi = 3.14; double *ptr = π // 错误,不是指向常量的指针 const double *cptr = π *cptr = 42; // 错误指向常量的指针不能改变指向的值 int err_num = 0; int *const curErr = &err_num; // 常量指针 curErr只能一直指err_num
预处理器:预处理器是在编译之前执行的一段程序,当预处理器看到#include 标志是就会用指定的头文件的内容代替#include。
头文件保护符:头文件保护符依赖于预处理变量(无视作用域的规则),预处理变量有俩种状态:已定义和未定义。#define 指令把一个名字设定为预处理变量,另外俩个指令则是分别检查某个指定的预处理变量是否已经定义:#ifdef 当且仅当变量已定义时为真,#ifndef 当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif 指令为止。
// 防止重复包含的发生 #ifndef SALES_DATA_H #define SALES_DATA_H ..... #endif
string类型:可变长的字符序列,使用string必须包含string头文件,string的定义在std命名空间中.
string::size_type类型:string.size()返回的是一个size_type类型(无符号整数 类型)
// 必须包含 #include <string> using std::string; // 初始化方式 string s1; // 默认初始化,s1是一个空串 string s2(s1); // s2是s1的副本 string s2 = s1; string s3 = "value"; // s3是字面值value的副本,除了字面值最后的那个空字符外 string s4(n,'c'); // 把s4初始化为连续n个字符c组成的串 // string操作 os << s; // 将s写到输出流os当中,返回os is >> s; // 从is中读取字符串赋给s,字符串以空白分隔,返回is getline(is,s) // 从is中读取一行赋给s,返回is s.empty() // s 为空返回true s.size() // 返回s中字符的个数 s[n] // 返回s中第n个字符的引用,位置从0开始 s1 + s2 // 返回s1 + s2连接后的结果 s1 = s2 // s2 副本代替s1中原来的字符 s1 == s2 // 如果s1 和 s2 中所含的字符完全一样,则它们相等,大小写敏感 s1 != s2 < > >= <= .... // string 处理函数 isspace(s) // 当s是空白时为真(s是空格、横向制表符、纵向制表符、数字、字母、可打印空白的一种) tolower // 变小写 toupper(s) // 变大写
vector:表示对象的集合,其中所有对象的类型都相同。
// 初始化 vector<T> v1; // v1是一个空vector,它潜在的元素类型是T类型,执行默认初始化 vector<T> v2 = v1; vector<T> v3(n,val); // v3包含了n个重复的元素,每一个元素都是val vector<T> v4(n); // v4包含n个重复执行值初始化的对象,初始化方式由具体的T类型来决定 vector<T> v5 = {a,b,c}; // v5 包含了初始化个数的元素,每一个元素被赋予相应的初始值 // vector 操作 v.empty() v.size() // 返回类型是vector<int>::size_type(对应元素的size_type) v.push_back(t); // 在尾部加入元素 v[n];
数组:
int *ptrs[10]; // ptrs是含有10个整型指针的数组,相当于二维数组 int (*parray)[10] = &arr; // parray指向一个含有10个整数的数组
类型转换:
隐式转换:
1.大多数表达式中,比int类型小的整型值首先提升为较大的整型类型。
2.在条件中,非布尔值转换为布尔值
3.初始化过程中,初始值转换成变量的类型;在赋值语句中,右值运算对象转换成左侧运算对象的类型。
4.如果算术运算符或关系运算的运算对象有多种类型,需要转换成同一种类型。
5.函数调用时也会发生类型转换
转换原则:
1.首先执行整型提升。如果提升后都是有符号或者无符号的,则小类型转换成大类型。
2.一个是有符号,一个是无符号类型:
1.如果无符号大于等于有符号类型,则有符号转换成无符号
2.如果无符号小于有符号:(1)如果无符号的所有值都可以存在有符号类型中,则无符号转换成有符号,否则有符号转换成无符号。
异常处理:
throw语句:使用throes引发异常
try语句块:try语句抛出的异常会被某个catch子句处理。
if(a != b) throw runtime_error("Data must refer to same ISBN"); try{ 主语句; }catch(异常声明){ 异常处理语句; }catch(异常声明){ 异常处理语句; } try{ .... }catch(runtimr_erroe{ cout << "..." << endl; .... }
函数:实参是形参的初始值,形参和实参类型必须匹配,但是实参能转换成形参类型也可以。
含有可变形参的函数:initializer_list 类型
// initialize_list 操作 initialize_list<T> lst; // 默认初始化。T类型元素的空列表 initialize_list<T> lst{a,b,c,d}; // lst的元素数量和初始值一样多,lst的元素是对应初始值的副本,列表中的元素是const lst2(lst); // 拷贝或赋值一个initialize_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素 lst2 = lst; lst.size(); lst.begin(); lst.end(); // 例子 void error_msg(initialize_list<string> s){ for(auto beg = s.begin();beg != s.end();beg++){...} } erroe_msg({"function","...","..."}); // 放在{}中进行值传递
函数重载:如果同一作用域内的几个函数名字相同但形参列表不同。不允许俩个函数除了返回类型外其他要素都相同。有顶层const的形参无法和没有顶层const的形参区分开。
类:
this指针:成员函数可以通过一个名为this的额外的隐式参数来访问调用他的那个对象。是一个常量的指针,不能修改this保存的地址,所以他不能指向常量对象,常量对象不能调用非常量成员函数。
常量成员函数:const放在成员函数的参数列表后。实际是改变了this指针的类型,改变为指向常量的常量指针。
std::string Sales_data::isbn() const {return bookNo;}
构造函数:定义对象的初始化方式,类通过一个或几个特殊的成员函数来控制器对象的初始化过程。如果没有定义构造函数,那么编译器就会隐式的定义一个默认的构造函数。默认构造函数按照如下规则初始化类的数据成员:1.如果存在类内的初始值,用他来初始化类的数据成员。 2.默认初始化该成员。
struct Sales_data{ Sales_data() = default; // 默认构造函数 // // 初始值是成员名字的一个列表,每一个名字后面紧跟着括号括起来(或者在花括号中)的成员初始值。{}是构造体,会执行 Sales_data(const std::string &s):bookNo(s){} Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenus(p*n){} Sales_data(std::istream &){read(is,*this)} std::string isbn() const {return bookNo;} Sales_data& combine(const Sales_data&); double avg_price() const; std::string boonNo; unsigned units_sold = 0; double revenus = 0.0; };
访问控制和封装:(编译器处理完类中的全部声明后才会处理成员函数的定义,仅仅针对函数名,而返回类型,形参列表则不是)
public:定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
private:定义在private说明符之后的成员可以被类的成员函数访问,但不能被使用该类的代码访问,private部分封装了类的实现细节。
友元:允许其他类或者函数访问它的非公有成员。增加一条以friend关键字开始的函数声明语句。友元不存在着传递关系。友元在类内的声明与定义的作用只是影响访问权限,而非普通意义上的声明,所以也不是类的成员函数。
如果想令Screen的友元函数是Window_mgr类的成员函数clear,则应该先定义Window_mgr类,但不能定义clear函数,接下来定义Screen类,包括对clear的友元声明,最后定义clear,此时他才可以使用Screen的成员。
内联函数:如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。类内部的成员函数都是默认内联的。
可变数据成员:可以被任意的函数修改值的数据成员,即使是在一个常量的成员函数中。在变量的声明前加入mutable关键字。
静态成员:只与类本身相关,而与类的对象无关。在类外定义静态成员,不能加static关键字,static只能出现在类内部。一般在类外初始化静态成员,如果在类内初始化则初始值应该是常量表达式。
class Sales_data{ // 为Sales_data的非成员函数所做的友元声明 friend Sales_data add(const Sales_data&,const Sales_data&); friend std::istream &read(std::istream&,Sales_data&); friend std::ostream &print(std::ostream&,cosnt Sales_data&); public: Sales_data() = default; // 默认构造函数 // 初始值是成员名字的一个列表,每一个名字后面紧跟着括号括起来(或者在花括号中)的成员初始值。{}是构造体,会执行 // 初始化顺序与初始值列表顺序无关,只与类内定义的数据成员顺序有关 Sales_data(const std::string &s):bookNo(s){} Sales_data(const std::string &s,unsigned n,double p):bookNo(s),units_sold(n),revenus(p*n){} Sales_data(std::istream &){read(is,*this)} std::string isbn() const {return bookNo;} Sales_data& combine(const Sales_data&); private: double avg_price() const{return units_sold ? revenue/units_sold : 0;}; std::string boonNo; unsigned units_sold = 0; double revenus = 0.0; };
IO类:fstream和sstream都是继承与iostream。IO对象无拷贝和赋值。
头文件 | 类型 |
iostream | istream,wistream从流读取数据 |
ostream,wostream向流写入数据 | |
iostream,wiostream读写流 | |
fstream | ifstream,wifstream从文件读取数据 |
ofstream,wofstream向文件写入数据 | |
fstream,wfstream读写文件 | |
sstream | istringstream,wistringstream从string读取数据 |
ostringstream,wostringstream从string写入数据 | |
stringstream,wstringstream读写string |
// IO库条件状态 strm::iostate // strm是一种IO类型,iostate是一种机器相关的类型,提供了表达条件状态的完整功能 strm::badbit // 用来指出流已经崩溃 strm::failbit // 用来指出一个IO操作失败了 strm::eofbit // 指出流到达了文件结束 strm::goodbit // 指出流未处于错误状态,此值为0 s.eof () // eofbit 被置位,到达文件结束,返回true s.bad() s.good() s.clear() // 将流中所有条件状态位复位,将流的状态设置为有效 s.clear(flags) // 复位给定的标志位 s.setstate(falgs) // 复位给定的标志位 s.rdstate() // 返回流s的当前状态
管理输出缓存(程序出错不会刷新缓冲区):每一个输出流都管理一个缓存区。数据并不会立刻打印出来,而是放在了缓冲区。
缓存刷新原因:1.程序正常结束,作为main函数的return操作的一部分,缓存刷新被执行。 2.缓存区满时 3.显示刷新缓存区 4.在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区。 5.一个输出流可能被关联到另一个流,当读写流被关联时,关联到的流的缓冲区会被刷新。如cin和cerr都被关联到cout,读cin或cerr都会导致cout的缓冲区被刷新。
cout << "Hi!" << endl; // 输出Hi和一个换行,然后刷新缓冲区 cout << "Hi" << flush; // 输出Hi,然后刷新缓冲区,不加额外的字符 cout << "Hi!" << endsl // 输出Hi和一个空字符,刷新缓冲区
unitbuf操纵符:每一次输出都刷新缓冲区。
nounitbuf操纵符:恢复使用正常的系统管理的缓冲区刷新机制。
cout << unitbuf; // 所有输出操作后都会立即刷新缓冲区 cout << nountibuf; // 回到正常的缓冲机制
文件输入输出:
fstream fstrm; // 创建一个未绑定的文件流,fstream是头文件fstream中定义的一个类型 fstream fstrm(s) // 创建一个fstream,并打开名为s的文件,s可以是string类型,或者是C风格的字符串的指针。 fstream fstrm(s,mode); // mode方式打开文件 fstrm.close() // 关闭frsm绑定的文件 fstrm.is_open() // 返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭
文件模式(mode方式):
in // 以读方式打开 out // 以写方式打开 app // 每次写操作前均定位到文件末尾 ate // 打开文件后立即定位到文件末尾 trunc // 截断文件,即清空文件 binary // 以二进制方式打开IO ofstream app("file",ofstrem::out | ofstream::app)
string流:
sstream strm // strm是一个未绑定的stringstream对象。sstream是头文件sstream中定义的一个类型 sstream strm(s); // strm是一个sstream对象,保存string s的一个拷贝。此构造函数是explicit的 strm.str() // 返回strm所保存的string的拷贝 strm.str(s) // 将string s拷贝到strm中 #include <iostream> #include <sstream> #include <string> int main() { std::istringstream in; in.str("hello world 233"); std::string word; while (in >> word) { std::cout << word << std::endl; } return 0; } /* 输出: hello world 233 */
顺序容器:
swap:交换俩个相同类型容器的内容,只是交换了俩个容器的内存地址,即指针的地址交换了。(array则是交换元素,string在调用swap后,迭代器、引用、指针都会失效。)
vector // 可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢 deque // 双端队列,支持快速随机访问,在头尾位置插入、删除速度很快,是一种多段存储空间连接起来的存储空间 list // 双向链表,只支持双向顺序访问,在链表任何位置进行插入、删除操作速度很快 forword_list // 单向链表,只支持单向顺序访问,在链表任何位置进行插入、删除操作速度很快 array // 固定数组大小,支持快速随机访问,不能添加或删除元素 string // 与vector相似的容器,但专门用于保存字符,随机访问快,在尾部、删除速度快
*********向顺序容器添加元素的操作(vector、string、deque添加元素则指向容器的迭代器、引用、指针会失效),内存会重新分配************
forward_list 有自己专有版本的insert和emplace
forward_list 不支持push_back,emplace_back
vector,string不支持push_front,emplace_front
c.push_back(t) 尾部插入一个元素
c.insert(p,t) 在迭代器p指向的元素之前创建一个值为t的元素,返回指向信添加的第一个元素的迭代器
c.insert(p,n,t) 将n个t元素插入到迭代器p指向的元素之前,返回指向信添加的第一个元素的迭代器
c.insert(p,b,e) 将迭代器b和e指向的范围内的元素插入到迭代器p指向的元素之前,返回指向信添加的第一个元素的迭代器
**********顺序容器删除操作*********
c.pop_back()
c.pop_front()
c.erase(p) 删除迭代器p所指的元素,返回一个指向被删元素之后元素的迭代器
c.erase(b,e) 删除迭代器b到e的元素
c.clear() 删除c中的所有元素,返回void
**********forward_list的插入和删除操作*********
lst.before_begin() 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用
lst.insert_after(p,t) 在迭代器p之后的位置插入t元素,n是数量
lst.insert_after(p,n,t)
lst.insert_after(p,b,e)
lst.insert_after(p,il)
lst.erase_after(p) 删除p之后的元素
lst.erase_after(b,e) 删除迭代器b到e(不包括)的元素
****顺序容器大小操作*********
c.resize(n) 调整c为大小是n的容器,若小于原来的大小则舍弃,否则对新加入的元素执行默认初始化
c.resize(n,t)
栈适配器:
stack<int> intstack; // 空栈 for(szie_t ix = 0;ix != 10;ix++) instack.push(ix); // 插入元素 while(!instack.empty()){ int value = instack.top(); // 返回栈顶元素,但不弹出 instack.pop(); // 弹出栈顶元素 }
队列适配器:
// queue默认基于deque实现 q.empty() // 如果队列空则返回真 q.pop() // 删除第一个元素 q.front() // 返回第一个元素 q.back() // 返回最后一个元素 q.top() // 返回最高级别元素 q.push(t) // 在末尾加入一个元素
泛型算法(不依赖与对象,只依赖元素类型的操作,如遍历的迭代器):大部分定义在头文件algorithm中,标准库还在头文件numeric中定义了一组数值泛型算法。
大部分泛型算法的前俩个参数是范围迭代器(指针,只要能确认范围)。只要确定泛型算法的内部操作的进行则可以用此算法。
// 对vec中的元素求和,和的初值为0 。 此泛型算法是默认元素可以+操作 int sum = accumulate(vec.cbegin(),vec.cend(),0); string sum = accumulate(vec.cbegin(),vec.cend(),string(""));
向泛型算法传递函数:
void elimDups(vector(string> &word){ sort(word.begin(),word.end()); // 按字典序排序 auto end_unique = unqiue(word.begin(),word.end()); // 重排输入范围,使得每个单词只出现一次,返回不重复区域之后一个位置的迭代器。 word.erase(end_unqiue,word.end()); // 删除重复元素 } bool isShorter(const string &s1,const string &s2){ return s1.size() < s2.size(); } elimDups(word); stable_sort(words.begin(),word.end(),isShorter); // 按长度重新排序,长度相同的单词维持字典序
lambda:可调用对象,可以向泛型算法传递。捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在他所在函数之外声明的名字。
[捕获列表] (参数列表) -> 返回类型 {函数体} 捕获列表:lambda所在函数中定义的局部变量的列表(通常为空),是初始化方式,在定义时就创建捕获列表的值,参数列表和返回类型可以为空,但是捕获列表和函数体不能为空 auto f = []{return 42;}; // f是一个lambda对象,没有参数列表和返回类型 [sz](const string &a){return a.size >= sz;};
标准库bind函数(参数绑定):
auto newCallable = bind(callable,arg_list); newCallable本身是一个可调用对象,arg_list是一个用逗号分隔的参数列表,对应给定的callable的参数。 当我们调用newCallable时自动调用callable,并传递给他arg_list中的参数。 arg_list中的参数可能包含形如_n的名字,其中n是一个整数,为占位符,如_1是newCallable的第一个参数。
using namespace std::placeholders; // _1占位符定义在此命名空间上
auto check6 = bind(check_size,_1,6); string s = "hello"; bool b1 = check6(s); // check6会调用check_size(s,6) auto wc = find_if(word.begin(),word.end(),[sz](const string &s)); // 相当于 auto wc = find_if(word.begin(),word.end(),bind(check_size,_1,sz));
迭代器:定义在 iterator。
插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素。接受一个容器,生成一个迭代器,能实现向给定容器添加元素。当我们通过一个插入迭代器进行赋值时,该迭代器调用容器操作来向给定容器的指定位置插入一个元素。
插入器有三种类型: back_inserter:创建一个使用push_back的迭代器,调用push_back(t)函数 front_inserter:调用push_front(t) inserter:调用insert(t,p),第一个参数是容器,第二个参数是指向给定容器的迭代器 // it是有inserter生成的迭代器。 *it = val; // 等价于 it = c.insert(it,val); ++it;
流迭代器:绑定到输入流或输出流,可用来遍历所关联的IO流。用泛型算法从流对象读取数据或者写入数据。俩个类型:istream_iterator:读取输入流 ostream_iterator:向一个输出流写数据。
// 用istream_iterator从标准输入读取数据,存入一个vector的例子 istream_iterator<int> in_iter(cin); // 从cin读取int istream_iterator<int> eofl; // istream尾后迭代器 while(in_iter != eof){ vec.pauh_back(*in_iter++); // 使用算法操作迭代器 istream_iterator<int> in_iter(cin),eof; cout << accumulate(in,eof,0) <,endl; //ostream_iterator输出值的序列 ostream_iterator<int> out_iter(cout," "); // out将类型为int的值写入输出流os中,每一个值都输出一个" "。 for(auto e:vec) *out_iter++ = e; cout << endl;
反向迭代器:这些迭代器向后而不是向前移动。除了forward_list之外的标准库容器都有反向迭代器。递增递减操作会颠倒过来,递增会向前移一个单位。调用rbegin、rend、crbegin、crend来获得反向迭代器。
vector<int> vec = {0,1,2,3,4,5,6,7,8,9}; // vec.crbegin()是指向尾元素 for(auto r_iter = vec.crbegin();r_iter != vec.crend();r_iter++) cout << *r_iter << endl;
移动迭代器:这些专业的迭代器不是拷贝其中的元素,而是移动它们。
关联容器:高效的关键字查找和访问。
map:map的元素是一些关键字-值对(key-value),关键字起到索引的作用,值则表示与索引相关联的数据。
set:每个元素只包含一个关键字,支持高效的关键字查询操作-->检查给定的关键字是否在set中。
*****按关键字有序保存元素(根据key的默认 < 运算符来排序 ) map:key-value对,在map头文件中 set:关键字即值,在set头文件中 multimap:关键字可重复出现,保存在map头文件中 multiset:关键字可重复出现,保存在set头文件中 *****无序集合 unordered_map:用哈希函数组织的map,在unordered_map头文件中 unordered_set:用哈希函数组织的set,在unordered_set头文件中 unordered_multimap:哈希组织的map,关键字可以重复,在unordered_map头文件中 unordered_multiset:哈希组织的set,关键字可以重复,在unordered_set头文件中
// 初始化器必须能转换为容器中元素的类型 set<string> exclude = {"the","but","and"};
set<int> iset(ivec.begin(),ivec.end()); map<string,string> authors = {{"joyce","james"},{"austen","jane"}};
使用关键字类型的比较函数:用尖括号指出要定义那种类型的容器,自定义的操作类型必须在尖括号中紧跟着元素类型给出。
bool compareIsbn(const Sales_data &lhs,const Sales_data &rhs){ return lhs.isbn() < rhs.isbn(); } multiset<Sales_data,decltype(compareIsbn)*> bookstore(compareIsbn); // 用compareIsbn来初始化bookstore对象,表示当我们向bookstore添加元素时,通过compareIsbn来为这些元素排序
pair类型:定义在utility头文件中。一个pair保存两个数据成员,是一个用来生成特定类型的模板,当创建一个pair时,我们必须提供俩个类型名,pair的数据成员将具有对应的类型。俩个成员是public,分别为first和second。
// pair 操作 pair<T1,T2> p; // p是一个pair,俩个成员分别为T1、T2进行值初始化 pair<T1,T2> p{v1,v2}; // 分别用v1、v2进行初始化 pair<T1,T2> p = {v1,v2}; make_pair(v1,v2); // 返回一个用v1和v2初始化的pair,pair的类型从v1、v2中推断出来 p.first // 返回p的名为first的数据成员 p.second // 返回p的名为second的数据成员 p1 relop p2; // 关系运算符,按字典序定义 p1 == p2; p1 != p2; pair<string,string> anon; // 保存俩个string,俩个空串 pair<string,size_t> word_count; // 空串和0 pair<string,vector<int>> line; // 空串和空vector pair<string,string> author{"james","joyce"};
关联容器操作:
key_type:此容器类型的关键字类型 mapped_type:每个关键字关联的类型,只适用于map value_type:对于set,于key_type相同;对于map,为pair(const key_type,mapped_type)
insert的返回值:对于不包含重复关键字的容器,返回一个pair,pair.first是一个指向给定关键字的元素的迭代器,pair.second是一个bool值,表示是否插入成功。对于重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器,这里无需返回一个bool值,因为insert总是向这类容器中加入一个新元素。
// 关联容器insert操作 c.insert(v) // v是value_type类型的对象,args用来构造一个元素,对于map和set来说,只有当元素不存在的时候才会插入 c.emplace(args) c.insert(b,e) // b,e是迭代器 c.insert(il) // il是花括号列表 c.insert(p,v) // 将迭代器p作为一个提示器,指出从哪里开始搜索新元素应该存储的位置,返回一个指向给定关键字的迭代器
// 删除操作 c.erase(k); // 从c中删除每个关键字为k的元素,返回一个size_type值,指出删除的元素的数量 c.erase(p); // 从c中删除迭代器p指定的元素,p必须指向c中一个真实的元素,返回一个指向p之后元素的迭代器,若p指向c中尾元素则返回p.end() c.erase(b,e) // b,c是迭代器
// map和unordered的下标操作,下标操作与解引用返回类型不同,前者返回mapped_type类型,后者返回vlaue_type 类型是一个pair类型 c[k] // 返回关键字k的元素;如果k不在c中,添加一个关键字为k的元素,对其进行值初始化 c.at[k] // 访问关键字为k的元素,带参数检查,若k不在,抛出out_of_range异常
multimap和multiset中查找元素:如果一个multimap、multiset中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。(先统计个数,在找出第一个出现的位置。进行count此循环提取元素或者使用lower_bound和upper_bound或equal_range来解决)
*******查找操作 lower_bound 和 upper_bound不适应与无序容器 下标和at操作只适用于非const的map和unordered_map c.find(k) // 返回一个迭代器,指向第一个关键字为k的元素,若k不在容器中,则返回尾后迭代器 c.count(k) // 返回关键字等于k的元素的数量,对于不允许重复的容器,返回值永远是0和1 c.lower_bound(k) // 返回一个迭代器,指向第一个关键字不小于k的元素 c.upper_bound(k) // 返回一个迭代器,指向第一个关键字大于k的元素 c.equal_range(k) // 返回一个pair迭代器,表示关键字等于k的元素范围,若k不存在,pair的俩个成员均等于c.end()
动态内存:用堆来存储动态分配的内存,必须显式的销毁它们。new和delete。智能指针(定义在memory):1.share_ptr 2.unique_prt 3.weak_ptr
unique智能指针:只能指向一个对象。没有赋值操作。
.
shared_ptr和unique都支持的操作 shared_ptr<T> sp 空智能指针,可以指向类型为T的对象 unique_ptr<T> up p.get() 返回p中保存的指针,若智能指针释放了其对象,返回的指针所指向的对象就消失了 swap(p,q) 交换p、q的指针 share_ptr独有的操作 make_shared<T>(args) 返回一个shared_ptr,指向一个拥有动态分配的类型为T的对象,用args初始化 shared_ptr<T>p(q) p是shared_ptr q的拷贝,此操作会递增q中的计数器 p = q p和q都是shared_ptr,所保存的指针必须能互相转换,会递减p的引用计数,递增q的引用计数 p.count() 若p.use_count() 为1则true,否则为false p.use_count() 返回p共享对象的智能指针数量
最安全是调用make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化他。shared_ptr 有一个引用计数器,如果为0,则自动释放内存。
shared_ptr<int> p3 = make_shared<int>(42); // 指向一个值为42的int的shared_ptr shared_ptr<int> p3 = make_shared<int>(); // 指向一个值初始化的int,即值为0
new:分配内存,执行默认初始化或调用类的构造函数 delete:销毁内存,必须是动态分配的指针或空指针。
vector<int> *p = new vector<int>{0,1,2,3,4,5,6,7,8,9}; vector<int> *p = new vector<int>; delete p;
拷贝构造函数(非static成员):第一个参数是自身类类型的引用,且任何额外参数都有默认值。如果没有编译器会帮我们合成一个。每一个成员的类型决定了如何拷贝,对于类类型的成员,会使用其拷贝构造函数来拷贝,内置类型则直接拷贝。
当我们使用直接初始化时,我们实际是要求编译器使用普通的函数匹配来选择与我提供的参数最匹配的构造函数。当我们使用拷贝初始化时,我们要求编译器将右侧运算符对象拷贝到正在创建的对象中,如果有需要的话还要进行类型转换。
拷贝函数发生的场景:1. 将一个对象作为实参传递给一个非引用类型的形参(参数传递会发生) 2.从一个返回类型为非引用类型的函数返回一个对象(return会发生) 3.用花括号列表初始化一个数组中的元素或者一个聚合类中的成员。
class Foo{ Foo(); // 默认构造函数 Foo(const Foo&); // 拷贝构造函数 }
拷贝赋值运算符:可以控制其对象如何赋值,如果未定义则编译器会合成一个。赋值运算符通常返回一个指向其左侧运算对象的引用。
重载运算符:本质上是一个函数,其名字由operator关键字后表示要定义的运算符的符号组成。
class Foo{ public: Foo& operator=(const Foo&); // 赋值运算符 };
析构函数(非static成员):释放对象使用的资源。是类的成员函数,名字由波浪号接类名构成,没有返回值也不接受参数。
class Foo{ public: ~Foo(); // 析构函数 };
阻止拷贝:可以将拷贝构造函数和拷贝运算符定义为删除的函数来阻止拷贝。
struct Nocopy{ Nocopy() = default; // 使用合成的默认构造函数 Nocopy(const Nocopy&) = delete; // 阻止拷贝 Nocopy &operator=(const Nocopy) = delete; // 阻止拷贝 ~Nocopy() = default; // 合成的析构函数 };
OOP:
派生类:通过使用派生列表明确指出它是从哪些基类继承过来的。首先是冒号,后面紧跟以逗号分隔的基类列表,其中每个基类前面可以有访问说明符。
定义基类:
class Quote{ public: Quote() = default; Quote(const string &book,double sales_price): bookNo(book),price(sales_price){} string isbn() const{return bookNo;} // 虚函数标志 virtual double net_price(size_t n) const{return n * price;} virtual ~Quote() = default; private: string bookNo; protected: // 只能派生类访问 double price = 0.0; };
定义派生类:
class Buik_quote:public Qutoe{ public: Buik_quote() = default; Buik_quote(const string&,double,size_t,double); double net_price(size_t) const override; private: size_t min_qty = 0; double discount = 0.0; };
派生类对象和基类对象类型可以相互转换(只针对指针和引用),派生类对象可以绑定到基类的指针上,但基类不可以绑定到派生类指针上,因为基类的对象可能不是派生类的一部分。类的多态的基础,形参为基类类型的指针或者引用,但是实际调用的是基类对象还是派生类的对象都是你确定的。
派生类向基类的可访问性:
1.只有当D公有的继承B时,用户代码(不是类内部)才能使用派生类向基类转换。
2.不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换。
3.D继承B的方式是公有或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换。
静态成员:如果基类定义了一个静态成员,则在整个继承体系中存在该成员的唯一定义。通过::来访问它,基类、派生类、对象都可以访问。
派生类的声明包含类名但不可以包含派生列表。想将某类作为基类,则该基类必须已经定义而非声明。
静态类型是编译时已知的类型,动态类型是变量或者表达式表示的内存中的对象的类型,即指针指向的对象的类型。
虚函数:当我们使用基类的引用或指针调用一个虚函数时会执行动态绑定。
派生类的构造函数不能初始化基类的成员,只能通过自己的构造函数的初始化列表传递给基类构造函数。首先初始化基类成员,再按照声明的顺序依次初始化派生类的成员。
Bulk_quote(const string &book,double p,size_t qty,double disc): Qutoe(book,p),min_qty(qty),discount(disc){} };
override:说明派生类中的虚函数,如果没有覆盖基类的函数则会报错。
final:与override一样放在函数参数列表后面,表示不能被覆盖,否则会报错。
纯虚函数:是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”,有纯虚函数的类为抽象基类,我们不能创建一个抽象基类。
double net_price(size_t) const = 0;
派生访问说明符:说明基类的成员对于派生类来说是public或者是private的,是进一步的约束。