C++学习笔记

367 阅读18分钟

1、inline 关键字

  • 使用内联函数可以节省运行时间,因为编译的时候,内联函数相当于直接把代码copy到调用处,可以节省函数调用的堆栈开销。防止递归堆栈溢出
  • 一般只将规模很小,使用频繁的函数生命为内联函数,从代码整洁角度来说,这是很合理的(但是真正的原因是?)。
  • 内联函数不能包含复杂的控制语句。
  • 类的成员函数也可以指定为内联函数。

2、 通过引用变量访问对象中的成员

  • 引用变量相当于为一个变量起别名,有时候他的功效和指针很像,底层是怎么实现的有待探讨。

3、构造函数的规则

  • 构造函数不需要用户调用,而是在建立对象时候自动调用(注意,这个在对象没有初始值的时候,是不会调用的,这个说话有误。)
  • 构造函数的名字必须和类名字相同,而不能有用户任意命名
  • 构造函数不具有任何类型,不返回任何值
  • 构造函数的功能有用户定义的,用户根据初始化的要求设计函数体和函数参数
  • 带默认参数的构造函数,会和默认构造函数产生歧义,比如 student(); 和 student(int age = 0); 会导致编译错误。 正确的写法为 正确的写法:
    student();
    student(int);   // 不带默认参数

4、关于释构函数的注意点

  • 释构函数不返回任何值,没有函数类型,也没有函数参数
  • 释构函数不能被重载,一个类可以有多个构造函数,但是只能有一个释构函数
  • 释构函数的作用并不仅限于释放资源,它还可以被用来执行”用户希望在最后一次使用对象之后所执行的任何操作“
  • 如果用户没有定义释构函数,C++编译系统会自动生成一个释构函数

5、const关键字的作用

常对象

  • 用const修饰的对象,里面的所有成员变量不能被修改

  • 凡是希望数据成员不被修改的对象,都可以声明为常对象,声明方法: const object(0) 或者 object const(0);

  • 常对象成员变量必须有初始值,否则编译报错。

指向对象的常指针

  • 将指针变量声明为const类型,其指针的值不能改变

  • 一般定义方式为 object * const abc;

  • object a(0) , b(0) ; object * const pt = &a; pt = &b; //错误

指向常变量的指针变量

  • const object * a / object const * a;

  • 如果一个变量已被声明为常变量/对象,只能用指向常变量/对象的指针指向它,而不能用指向非

  • 指向常变量的指针变量可以指向未被声明为const的变量,但不能通过此指针变量改变该变量的值。

  • 指向常对象的指针最常用于函数的形参,以保护形参指针所指向的对象在函数执行过程中不被修改。

void doSomething1(const Test *p1)
{
p1->setX(5); //非法!
p1->printxy( );
}

| 数据成员 | 非const成员函数 | const 成员函数 | | --- | --- || --- | | 非const数据成员 | 可引用,可改变值 | 可引用,不可改变值 | const数据成员 | 可引用,不可改变值 | 可引用,不可改变值 | const对象数据成员 | 不可引用,不可改变值 | 不可引用,不可改变值

6、对象的赋值和复制

  • 同类对象之前可以互相赋值
  • 对象赋值的一般形式为 obj1 = obj2
  • 对象赋值的实现原理是赋值运算符的重载

7、对象的复制

  • 用已有的对象克隆出一个新对象
  • 其一般形式为 类名 对象2(对象1);
  • 原理是编译系统提供默认复制构造函数
  • C++的所有类型都实现了复制构造函数,基本类型也是,比如 int a(5),调用int的赋值构造函数,把参数5传入生成一个int 类型。
  • 当函数参数为类对象的时候,调用函数时,实参赋值给形参会调用对象的复制构造函来生成一个实参的拷贝
  • 当函数返回值为对象时,在函数调用完毕,会复制一个对象返回给调用处

复制构造函数:

Object::Object(const Object& b){
    a = b.a
}

8、友元

友元函数

  • 如果在本类以外其他地方定义的函数,在类体中用friend进行声明,此函数成为本类的友元函数,该函数可以访问类的私有成员变量。
  • 友元函数可以属于不是任何类的非成员函数,也可以是其他类的成员函数。
  • 一个函数(普通函数和成员函数)可以声明为多个类的友元函数,这样就可以访问多个类的私有数据。

友元类

  • 如何一个类中声明了其他类为友元类,那么其他类就可以随意反问这个类的私有数据。
  • 一般情况下不需要用到友元类,虽然友元类有助于数据共享,但是严重影响了类的封装性,不利于信息隐藏,大多数问题用友元函数就能解决。

9、运算符重载

运算符重载的意义

  • 通过运算符重载,扩大C++已有运算符的作用,使用运算符可以作用于类
  • 使用运算符重载技术,能使程序易于编写,阅读和维护
  • 运算符被重载后,其原有的功能依然保留,没有丧失或改变
  • 运算符重载实质是函数的重载

运算符重载的实现方法

对象+法运算符重载
成员函数实现 
// 写法一
object object::operator+(object & obj2){
    object obj3;
    obj3.a = obj2.a;
    return obj3;
}

// 写法二
object object::operator+(object & obj2){
    // 直接调用构造函数返回
    return object(obj2.a);
}


非成员函数实现方案
object operator+(object & obj1, object & obj2){
    // 直接调用构造函数返回
    return object(obj1.a + obj2.a);
}

运算符重载的规则

  • 不允许创造新的运算符,只能对已有C++的运算符进行重载
  • C++ 不允许的重载运算符有5个分别是:
成员运算符.
成员指针访问运算符.*
作用域运算符::
sizeof
条件运算符?:
  • 重载不能改变运算符运算对象(就是不能改变运算符的操作数)
  • 重载不能改变运算符的优先级
  • 重载不能改变运算符的结合性
  • 重载的运算符的函数不能带有默认参数
  • 重载的运算符必需和用户定义的自定义类型对象一起使用,参数至少有一个是类对象或其引用

10、类(继承类)访问权限

  • 访问权限不影响类的内存布局
  • struct 成员变量和继承方式默认都是public
  • class 成员变量和继承方式默认都是private
  • 如果成员变量和继承方式同时都有访问权限限制,则遵从最小访问权限原则,比如类继承是protected,但是成员变量是private,那么最终这个成员变量的权限还是private

| * |public | protected | private | | --- | --- || --- | --- | | 同一个类 | YES | YES | YES | | 派生类 | YES | YES | NO | | 外部类 | YES | NO | NO |

11、匿名函数——lambda表达式

  • 声明Lambda 表达式
[capture list] (params list) mutable exception -> return type { function body}
各项具体含义
1. capture list 捕获外部变量列表
2. params list 形参列表
3. mutable 指示符,用于说明是否可以修改捕获的变量
4. exception 异常设定
5. return type 返回类型
6. function body 函数体

// 实例
int a0 = 1;
int b0 = 2;
auto funcs = [=, &b0](int c)->int {return b0 += a0 + c;};
int ress = funcs(6);

12、命名空间

  • 为了解决C++标准库中的标识符与程序 中的全局标识符之间以及不同库中的 标识符之间的同名冲突,标准C++库的 所有的标识符都定义在一个名为std的 命名空间中。
  • C++有一个默认的命名空间,为全局命名空间为::
  • 命名空间是ANSI C++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。
  • 同名字的命名空间会在编译过程中进行合并
  • 如果你指向引入命名空间中某个函数,变量。需要直接使用using 关键字
namespace Space {
    int aSpace;
};
using Space::aSpace;
using std::vector;

13、有继承关系的构造函数调用

  • 子类会默认调用父类的无参构造函数
  • 如果子类的构造函数显示地调用了父类的有参构造函数,就不会再去调用默认构造函数
  • 如果父类缺少无参构造函数,子类的构造函数必须显示地调用父类的有参构造函数

14、多态

静态多态

  • 函数重载和运算符重载实现的多态性属于静态多态性
  • 在程序编译时系统就能决定调用的是哪个函数,因此,又称编译时的多态性
  • 静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)

动态多态

动态多态性是在程序运行过程中才动态地确定操作所针对的对象,动态多态性又称运行时的多态性

  • C++默认没有实现动态多态,只会根据指针类型去调用对应的函数。
  • C++的动态多态通过virtual 函数来实现
  • 如果父类的函数是虚函数,子类重写该函数也是虚函数
  • 调用new object 和 new object() 是有区别的,后者会把成员变量初始化为0。

虚函数是指一个类中你希望重载的成员函数 ,当你用一个 基类指针或引用 指向一个继承类对象的时候,调用一个虚函数时, 实际调用的是继承类的版本。 

15、C++类型转换

  • 在C++中某些标准类型的数据之间可以自动转换
  • 隐式类型转换由C++编译系统自动完成的,用户不需要干预
int i  = 6;
i = 7.5 + i;
  • 强制(显式)类型转换是指在程序中将一中类型数据明确转换成另一指定的类型

const_cast

  • 一般用于去除const属性,将const转换成非const,比如
const Object obj1 = new Obejct();
Object * obj2 = static_cast<Object>(obj1);

const Person * pp1 = new Person();
//    pp1->age = 10; // error 因为是const 对象
Person * pp2 = const_cast<Person *>(pp1) ;
pp2->display();
pp2->age = 20;
pp2->display();

dynamic_cast

  • 一般用于多态类型的转换,有运行时安全检测。如果指针转换的时候,子类指针类型可以转换成父类,但是父类不能转换成子类。
class Person{
    virtual do();
};
class Student:public Person {
};
class Other{
};

Person * aa = new Person();
Person * bb = new Student();

Student * stu1 = dynamic_cast<Student*>(aa);// 返回为NULL
Student * stu2 = dynamic_cast<Student*>(bb);

Other * oth1 = dynamic_cast<Other*>(stu2); // 返回为NULL

//返回为NULL 证明类型转换失败

reinterpret_cast

  • 属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝
  • 可以交叉转换,可以将指针和整数互相转换

16、模板

  • C++中利用模板来实现泛型
  • 所谓的泛型,是一种将类型参数化以达到代码复用的技术
  • 模板的使用格式如下
template <typename\class T>
typenameclass是等价的
  • 模板没有被使用时,是不会被实例化出来的
  • 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误,所以一般将模板的声明和实现统一放到一个.hpp文件中

17、智能指针

传统的指针需要手动管理内存,非常容易导致内存泄露和野指针等问题,针对这一问题,C++采用了智能指针的方案来解决。

auto_ptr

  • auto_ptr 属于C++98标准,在C++11 已经不推荐使用(有缺陷,比如不能用于数组)
  • auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放
  • 一块动态内存只能被一个auto_ptr 对象持有。

shared_ptr

  • 被shared_ptr持有的对象会产生强引用,一个shared_ptr产生一个强引用计数
  • 可以通过shared_ptr的use_count 属性查看被引用对象的强引用计算器
  • 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
  • 当有一个shared_ptr销毁时,对象的强引用计算就会-1
  • 当一个对象的强引用计算为0是(没有任何shared_ptr指向对象时),对象就会自动销毁(调用释构)
  • shared_ptr 会产生循环引用的问题,可以通过weak_ptr解决循环引用的问题

unique_ptr

  • unique_ptr也会对对象产生强引用,但是它可以确保同一时间只有一个指针指向对象
  • 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁
  • 可以使用std::move 函数转移unique_ptr的所有权
unique_ptr<Object>ptr1(new Object());
unique_ptr<Object>ptr2 = std::move(ptr1); //转移所有权 

make_shared

利用这个模板函数对智能指针进行初始化效率更好

//传统初始化智能指针的写法
std::shared_ptr<Widget> spw(new Widget);
//利用make_shared 函数初始化的写法
auto spw = std::make_shared<Widget>();
  • 每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给Widget,还要一块内存分配给控制块。
  • std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用

18、联合体

  • 在查看oc runtime源码发现,原来C++的联合体 union 像结构体一样,可以编写构造函数,有private和public关键字,可以编写方法。。。。。
union aOBJ {
private:
    int value;
public:
    aOBJ(int value = 0){ this->value = value;};
    void say(){  std::cout << "saySomething" << value << std::endl;};
};

19、override关键字

  • C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。

20、 C++的左值右值

比如int x = 6;这里的x就是左值,6就是右值。 一个左值是指向一个指定内存的东西。另一方面,右值就是不指向任何地方的东西。通常来说,右值是暂时和短命的,而左值则活的很久,因为他们以变量的形式(variable)存在。我们可以将左值看作为容器(container)而将右值看做容器中的事物。如果容器消失了,容器中的事物也就自然就无法存在了。

& 操作符需要一个左值作为操作数,因为只有一个左值才拥有地址。

int* y = &5;//   error~

上面代码报错error: lvalue required as unary '&' operand

21、 delete和default关键字

delete有2个作用:

  1. 释放new分配的动态内存;
  2. 阻止编译器自动合成函数;

释放new分配的动态内存

delete需要与new配对使用,其参数可以是指向一块内存首地址或空指针(nullptr)。不能对同一块内存多次delete,但是可以对空指针多次delete。

注意delete和delete[] 的区别:

  • delete p 释放p指向的(单个)内存,可用于释放单个内存对象;
  • delete []p 释放p指向的内存区块,可用于释放一组内存(数组)

阻止编译器合成函数

适用于C++11以上版本。

如果没有为类编写构造函数、析构函数、拷贝构造函数、拷贝赋值运算符,以及移动拷贝函数、移动赋值运算符,编译器可能会为类合成默认的函数版本。显式使用delete,可以阻止编译器合成对应函数。 示例:

class MyObj {
public: 
    MyObj() = delete; // 阻止编译器合成构造函数,会导致类无  法实例化 
    MyObj& MyObj(const MyObj &) = delete; // 阻止编译器合成拷贝构造函数,  会导致类无法拷贝构造,如std::cin, std::cout 
    MyObj& operator=(const MyObj &) = delete; // 阻止合成赋值运算符,会导致类无法拷贝,如cin, cout, cerr ... }

与delete对应的是default,default显式告诉编译器使用编译器自动合成的函数。
合成的默认构造函数,只有基类子对象和类类型对象会被初始化,而其他所有的非静态成员(如整数,指针,数组等),都不会初始化,对他们进行初始化的应该是程序员,而非编译器。

如果类包含了需要动态创建对象的指针,就不能依靠合成的构造函数、拷贝构造函数、拷贝赋值运算符。

class MyObj {
public: 
    MyObj() = default; // 告诉编译器合成构造函数,作为默认构造函数,类的设计者不会实现该函数 
    MyObj& MyObj(const MyObj &) = default; // 告诉编译器合成拷贝构造函数,作为默认拷贝构造函数 
    MyObj& operator=(const MyObj &) = default; // 告诉编译器合成赋值运算符 ... 
 }

常用的对象

vector 对象

  • vector是表示可变大小数组的序列容器。
  • 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
  • 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
  • vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
  • 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
  • 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好。

vector 的头文件

#include<vector>

vector声明及初始化



vector<int> vec;		//声明一个int型向量
vector<int> vec(5);		//声明一个初始大小为5的int向量
vector<int> vec(10, 1);	//声明一个初始大小为10且值都是1的向量
vector<int> vec(tmp);	//声明并用tmp向量初始化vec向量
vector<int> tmp(vec.begin(), vec.begin() + 3);	//用向量vec的第0个到第2个值初始化tmp
int arr[5] = {1, 2, 3, 4, 5};	
vector<int> vec(arr, arr + 5);		//将arr数组的元素用于初始化vec向量
//说明:当然不包括arr[4]元素,末尾指针都是指结束元素的下一个元素,
//这个主要是为了和vec.end()指针统一。
vector<int> vec(&arr[1], &arr[4]); //将arr[1]~arr[4]范围内的元素作为vec的初始值

vector 基本操作

1、容量

  • 向量大小: vec.size();
  • 向量最大容量: vec.max_size();
  • 更改向量大小: vec.resize();
  • 向量真实大小: vec.capacity();
  • 向量判空: vec.empty();
  • 减少向量大小到满足元素所占存储空间的大小: vec.shrink_to_fit(); //shrink_to_fit

2、修改

  • 多个元素赋值: vec.assign(); //类似于初始化时用数组进行赋值
  • 末尾添加元素: vec.push_back();
  • 末尾删除元素: vec.pop_back();
  • 任意位置插入元素: vec.insert();
  • 任意位置删除元素: vec.erase();
  • 交换两个向量的元素: vec.swap();
  • 清空向量元素: vec.clear();

3、迭代器

  • 开始指针:vec.begin();
  • 末尾指针:vec.end(); //指向最后一个元素的下一个位置
  • 指向常量的开始指针: vec.cbegin(); //意思就是不能通过这个指针来修改所指的内容,但还是可以通过其他方式修改的,而且指针也是可以移动的。
  • 指向常量的末尾指针: vec.cend();

4、元素的访问

  • 下标访问: vec[index]; //并不会检查是否越界,但是如果index过大,超出了程序的栈区,就会直接crash,所以还是不要越界访问
printf("结果:%d\n",vec[100]); // 不会crash,而是直接读取内存偏移之后的数据
printf("结果:%d\n",avec[1000000000]); //直接crash,因为已经超出了栈区。
  • at方法访问: vec.at(1); //以上两者的区别就是at会检查是否越界,是则抛出out of range异常
  • 访问第一个元素: vec.front();
  • 访问最后一个元素: vec.back();
  • 返回一个指针: int* p = vec.data(); //可行的原因在于vector在内存中就是一个连续存储的数组,所以可以返回一个指针指向这个数组。这是是C++11的特性。

5、算法

  • 遍历
vector<int>::iterator it;
for (it = vec.begin(); it != vec.end(); it++)
    cout << *it << endl;
//或者,这种遍历越界会crash
for (size_t i = 0; i < vec.size(); i++) {
    cout << vec.at(i) << endl;
}
//或者,这种遍历方式就算越界,概率性不会crash。
for (int i = 0; i < 100; i ++) {
     cout <<  "数据:" << vec[i] <<endl;
}
  • 反转
#include <algorithm>
reverse(vec.begin(), vec.end());
  • 元素排序
#include <algorithm>
sort(vec.begin(), vec.end()); //采用的是从小到大的排序
//如果想从大到小排序,可以采用上面反转函数,也可以采用下面方法:
bool Comp(const int& a, const int& b) {
	return a > b;
}
sort(vec.begin(), vec.end(), Comp);

string 对象

构造函数

string(const char *s);    //用c字符串s初始化
string(int n,char c);     //用n个字符c初始化
string* str2 = new string("hello");
string aStr = "Hello world";

常用操作

字符串拼接,修改传入的参数,返回值也是新的字符串

string aStr = "Hello World";
string newStr  = aStr.append("ll");
cout << aStr ; //输出 Hello Worldll
cout << newStr ; //输出 Hello Worldll

字符串截取

string aStr = "Hello world";
string newStr = aStr.substr(0,2); // 表示从index == 0 开始,截取两个字符 
cout << aStr ;  // 输出Hello World
cout << newStr ; // 输出 He

字符串替换

  • string &replace(int p0, int n0,const char *s);//删除从p0开始的n0个字符,然后在p0处插入串s。
  • string &replace(int p0, int n0,const char *s, int n);//删除p0开始的n0个字符,然后在p0处插入字符串s的前n个字符
  • string &replace(int p0, int n0,const string &s);//删除从p0开始的n0个字符,然后在p0处插入串s
  • string &replace(int p0, int n0,const string &s, int pos, int n);//删除p0开始的n0个字符,然后在p0处插入串s中从pos开始的n个字符
  • string &replace(int p0, int n0,int n, char c);//删除p0开始的n0个字符,然后在p0处插入n个字符c
string aStr = "Tony say: Hello world";
string newStr = aStr.replace(0,9,"Space say:");
cout << aStr << "\n" ; // 输出Space say: Hello world
cout << newStr ;       // 输出Space say: Hello world

字符串查找

  • int find(char c, int pos = 0) const;//从pos开始查找字符c在当前字符串的位置
  • int find(const char *s, int pos = 0) const;//从pos开始查找字符串s在当前串中的位置
  • int find(const char *s, int pos, int n) const;//从pos开始查找字符串s中前n个字符在当前串
// 查找失败返回-1
string aStr = "Tony say: Hello world";
int newStr = aStr.find('s',3);
cout << aStr << "\n" ; // 输出 Tony say: Hello world 
cout << newStr ;       // 输出5
  • int find_first_of(char c, int pos = 0) const;//从pos开始查找字符c第一次出现的位置
  • int find_first_of(const char *s, int pos = 0) const;
  • int find_first_of(const char *s, int pos, int n) const;
string aStr = "Tony sss say sss: Hello world";
int newStr = aStr.find_first_of("sss",3);
cout << aStr << "\n" ; // 输出 Tony sss say sss: Hello world
cout << newStr ;       //输出5 

stack 对象

queue 对象

构造函数

queue<T> aQueue; //生成一个空的队列
queue<T> otherQueue(aQueue); //通过一个队列生成另外一个队列,注意泛型要保持一致

常用操作

  • front():返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
  • back():返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。
  • push(const T& obj):在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。
  • push(T&& obj):以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。
  • pop():删除 queue 中的第一个元素。
  • size():返回 queue 中元素的个数。
  • empty():如果 queue 中没有元素的话,返回 true。
  • emplace():用传给 emplace() 的参数调用 T 的构造函数,在 queue 的尾部生成对象。
  • swap(queue &other_q):将当前 queue 中的元素和参数 queue 中的元素交换。它们需要包含相同类型的元素。也可以调用全局函数模板 swap() 来完成同样的操作。

unordered_map 对象

unordered_map 是一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。

构造函数

unordered_map<int, string> myMapOne; //默认构造
unordered_map<int, string> myMap={{ 5, "张大" },{ 6, "李五" }};//使用{}赋值
unordered_map<int, string> myMapT(myMap); //通过拷贝构造函数构造

常用操作

  • begin | 返回指向容器起始位置的迭代器(iterator)
  • end | 返回指向容器末尾位置的迭代器
  • cbegin | 返回指向容器起始位置的常迭代器(const_iterator)
  • cend | 返回指向容器末尾位置的常迭代器
  • size 返回有效元素个数
  • max_size 返回 unordered_map 支持的最大元素个数
  • empty 判断是否为空
  • operator[] 访问元素
  • at 访问元素(如 m.at(5) = 3.33)
  • insert 插入元素
  • erase 删除元素
  • swap 交换内容
  • clear 清空内容
  • emplace 构造及插入一个元素
  • emplace_hint 按提示构造及插入一个元素
  • find 通过给定主键查找元素
  • count 返回匹配给定主键的元素的个数
  • equal_range 返回值匹配给定搜索值的元素组成的范围
  • bucket_count 返回槽(Bucket)数
  • max_bucket_count 返回最大槽数
  • bucket_size 返回槽大小
  • bucket 返回元素所在槽的序号
  • load_factor 返回载入因子,即一个元素槽(Bucket)的最大元素数
  • max_load_factor 返回或设置最大载入因子
  • rehash 设置槽数
  • reserve 请求改变容器容量

示例

    unordered_map<int, string> myMap={{ 5, "张大" },{ 6, "李五" }};//使用{}赋值
    myMap[2] = "李四";  //使用[ ]进行单个插入,若已存在键值2,则赋值修改,若无则插入。
    myMap.insert(pair<int, string>(3, "陈二"));//使用insert和pair插入
  
	//遍历输出+迭代器的使用
    auto iter = myMap.begin();//auto自动识别为迭代器类型unordered_map<int,string>::iterator
    while (iter!= myMap.end())
    {  
        cout << iter->first << "," << iter->second << endl;  
        ++iter;  
    }  
	
	//查找元素并输出+迭代器的使用
    auto iterator = myMap.find(2);//find()返回一个指向2的迭代器
    if (iterator != myMap.end())
	    cout << endl<< iterator->first << "," << iterator->second << endl;

25 pair 关键字

pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。

其标准库类型--pair类型定义在#include 头文件中,定义如下:

类模板:template<class T1,class T2> struct pair

参数:T1是第一个值的数据类型,T2是第二个值的数据类型。

功能:pair将一对值(T1和T2)组合成一个值,

        这一对值可以具有不同的数据类型(T1和T2),

        两个值可以分别用pair的两个公有函数first和second访问。

构造函数:

pair<T1, T2> p1;            //创建一个空的pair对象(使用默认构造),它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1(v1, v2);    //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。

常用操作

make_pair(v1, v2);          // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 < p2;                    // 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。
p1 == p2;                  // 如果两个对象的first和second依次相等,则这两个对象相等;该运算使用元素的==操作符。
p1.first;                   // 返回对象p1中名为first的公有数据成员
p1.second;                 // 返回对象p1中名为second的公有数据成员