C++提高编程

STl要点

string容器

一个string类

1、赋值操作

  • string& operator=(const char* s); //char*类型字符串 赋值给当前的字符串
  • string& operator=(const string &s); //把字符串s赋给当前的字符串
  • string& operator=(char c); //字符赋值给当前的字符串
  • string& assign(const char *s); //把字符串s赋给当前的字符串
  • string& assign(const char *s, int n); //把字符串s的前n个字符赋给当前的字符串
  • string& assign(const string &s); //把字符串s赋给当前字符串
  • string& assign(int n, char c); //用n个字符c赋给当前字符串

2、拼接操作

  • string& operator+=(const char* str); //重载+=操作符
  • string& operator+=(const char c); //重载+=操作符
  • string& operator+=(const string& str); //重载+=操作符
  • string& append(const char *s); //把字符串s连接到当前字符串结尾
  • string& append(const char *s, int n); //把字符串s的前n个字符连接到当前字符串结尾
  • string& append(const string &s); //同operator+=(const string& str)
  • string& append(const string &s, int pos, int n);//字符串s中从pos开始的n个字符连接到字符串结尾

3、查找和替换

  • int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
  • int find(const char* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
  • int find(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
  • int find(const char c, int pos = 0) const; //查找字符c第一次出现位置
  • int rfind(const string& str, int pos = npos) const; //查找str最后一次位置,从pos开始查找
  • int rfind(const char* s, int pos = npos) const; //查找s最后一次出现位置,从pos开始查找
  • int rfind(const char* s, int pos, int n) const; //从pos查找s的前n个字符最后一次位置
  • int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置
  • string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
  • string& replace(int pos, int n,const char* s); //替换从pos开始的n个字符为字符串s

4、比较操作

  • 字符串比较是按字符的ASCII码进行对比

= 返回 0

> 返回 1

< 返回 -1

  • int compare(const string &s) const; //与字符串s比较
  • int compare(const char *s) const; //与字符串s比较

5、存取操作

  • char& operator[](int n); //通过[]方式取字符
  • char& at(int n); //通过at方法获取字符

6、插入删除

功能描述:

  • 对string字符串进行插入和删除字符操作,从零开始

函数原型:

  • string& insert(int pos, const char* s); //插入字符串
  • string& insert(int pos, const string& str); //插入字符串
  • string& insert(int pos, int n, char c); //在指定位置插入n个字符c
  • string& erase(int pos, int n = npos); //删除从Pos开始的n个字符

7、获取字串

功能描述:

  • 从字符串中获取想要的子串

函数原型:

  • string substr(int pos = 0, int n = npos) const; //返回由pos开始的n个字符组成的字符串

vector容器

vector数据结构和数组非常相似,也称为单端数组

image

1、vector构造函数

  • vector<T> v; //采用模板实现类实现,默认构造函数
  • vector(v.begin(), v.end()); //将v[begin(), end())区间中的元素拷贝给本身。左闭右开
  • vector(n, elem); //构造函数将n个elem拷贝给本身。
  • vector(const vector &vec); //拷贝构造函数。

2、vector赋值操作

  • vector& operator=(const vector &vec);//重载等号操作符

  • assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。

  • assign(n, elem); //将n个elem拷贝赋值给本身。

3、vector容量和大小

  • empty(); //判断容器是否为空

  • capacity(); //容器的容量

  • size(); //返回容器中元素的个数

  • resize(int num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • resize(int num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除

4、插入删除

  • push_back(ele); //尾部插入元素ele
  • pop_back(); //删除最后一个元素
  • insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele
  • insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele
  • erase(const_iterator pos); //删除迭代器指向的元素
  • erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
  • clear(); //删除容器中所有元素

5、数据存取

  • at(int idx); //返回索引idx所指的数据
  • operator[]; //返回索引idx所指的数据
  • front(); //返回容器中第一个数据元素
  • back(); //返回容器中最后一个数据元素

6、互换容器

  • swap(vec); // 将vec与本身的元素互换

7、预留空间

  • 减少vector在动态扩展容量时的扩展次数
  • reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。

deque容器

1、基本概念

双端数组,可以对头端进行插入删除操作

deque与vector区别:

  • vector对于头部的插入删除效率低,数据量越大,效率越低

  • deque相对而言,对头部的插入删除速度回比vector快

  • vector访问元素时的速度会比deque快,这和两者内部实现有关

  • deque容器的迭代器也是支持随机访问的

2、构造函数

和vector构造方式几乎一直,灵活使用即可

  • deque<T> deqT; //默认构造形式
  • deque(beg, end); //构造函数将[beg, end)区间中的元素拷贝给本身。
  • deque(n, elem); //构造函数将n个elem拷贝给本身。
  • deque(const deque &deq); //拷贝构造函数

3、赋值操作

和vector构造方式几乎一直,灵活使用即可

  • deque& operator=(const deque &deq); //重载等号操作符

  • assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。

  • assign(n, elem); //将n个elem拷贝赋值给本身。

4、大小操作

deque没有容量的概念

  • deque.empty(); //判断容器是否为空

  • deque.size(); //返回容器中元素的个数

  • deque.resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

5、插入删除

两端插入操作:

  • push_back(elem); //在容器尾部添加一个数据
  • push_front(elem); //在容器头部插入一个数据
  • pop_back(); //删除容器最后一个数据
  • pop_front(); //删除容器第一个数据

指定位置操作:

  • insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
  • insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值。
  • insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值。
  • clear(); //清空容器的所有数据
  • erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。
  • erase(pos); //删除pos位置的数据,返回下一个数据的位置。

6、排序

  • sort(iterator beg, iterator end) //对beg和end区间内元素进行排序

stack容器

1、基本概念

概念:stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口

栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为

栈中进入数据称为 --- 入栈 push

栈中弹出数据称为 --- 出栈 pop

2、stack常用接口

构造函数:

  • stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
  • stack(const stack &stk); //拷贝构造函数

赋值操作:

  • stack& operator=(const stack &stk); //重载等号操作符

数据存取:

  • push(elem); //向栈顶添加元素
  • pop(); //从栈顶移除第一个元素
  • top(); //返回栈顶元素

大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小

queue容器

1、基本概念

概念:Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口

队列容器允许从一端新增元素,从另一端移除元素

队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为

队列中进数据称为 --- 入队 push

队列中出数据称为 --- 出队 pop

2、常用接口

构造函数:

  • queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
  • queue(const queue &que); //拷贝构造函数

赋值操作:

  • queue& operator=(const queue &que); //重载等号操作符

数据存取:

  • push(elem); //往队尾添加元素
  • pop(); //从队头移除第一个元素
  • back(); //返回最后一个元素
  • front(); //返回第一个元素

大小操作:

  • empty(); //判断堆栈是否为空
  • size(); //返回栈的大小

list容器

1、基本概念

将数据进行链式存储

链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的

链表的组成:链表由一系列结点组成

结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

STL中的链表是一个双向循环链表

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素

list的缺点:

  • 链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

总结:STL中List和vector是两个最常被使用的容器,各有优缺点

2、构造函数

list构造方式同其他几个STL常用容器,熟练掌握即可

  • list<T> lst; //list采用采用模板类实现,对象的默认构造形式:
  • list(beg,end); //构造函数将[beg, end)区间中的元素拷贝给本身。
  • list(n,elem); //构造函数将n个elem拷贝给本身。
  • list(const list &lst); //拷贝构造函数。

3、赋值和交换

list构造方式同其他几个STL常用容器,熟练掌握即可

功能描述:

  • 给list容器进行赋值,以及交换list容器

函数原型:

  • assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。
  • assign(n, elem); //将n个elem拷贝赋值给本身。
  • list& operator=(const list &lst); //重载等号操作符
  • swap(lst); //将lst与本身的元素互换。

4、大小操作

功能描述:

  • 对list容器的大小进行操作

函数原型:

  • size(); //返回容器中元素的个数

  • empty(); //判断容器是否为空

  • resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。

    ​ //如果容器变短,则末尾超出容器长度的元素被删除。

  • resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。

    //如果容器变短,则末尾超出容器长度的元素被删除。

5、插入删除

功能描述:

  • 对list容器进行数据的插入和删除

函数原型:

  • push_back(elem);//在容器尾部加入一个元素
  • pop_back();//删除容器中最后一个元素
  • push_front(elem);//在容器开头插入一个元素
  • pop_front();//从容器开头移除第一个元素
  • insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
  • insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
  • insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
  • clear();//移除容器的所有数据
  • erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
  • erase(pos);//删除pos位置的数据,返回下一个数据的位置。
  • remove(elem);//删除容器中所有与elem值匹配的元素。

6、数据存取

  • front(); //返回第一个元素。
  • back(); //返回最后一个元素。

7、反转排序

  • reverse(); //反转链表
  • sort(); //链表排序

对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序

高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂

set/multiset容器

1、基本概念

所有元素都会在插入时自动被排序

set/multiset属于关联式容器,底层结构是用二叉树(红黑树)实现。

set和multiset区别

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

2、set构造和赋值

构造:

  • set<T> st; //默认构造函数:
  • set(const set &st); //拷贝构造函数

赋值:

  • set& operator=(const set &st); //重载等号操作符

总结:

  • set容器插入数据时用insert
  • set容器插入数据的数据会自动排序

3、大小交换

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换两个集合容器

4、插入删除

  • insert(elem); //在容器中插入元素。
  • clear(); //清除所有元素
  • erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
  • erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
  • erase(elem); //删除容器中值为elem的元素

5、查找统计

  • find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
  • count(key); //统计key的元素个数

6、set和multiset区别

  • set不可以插入重复数据,而multiset可以
  • set插入数据的同时会返回插入结果,表示插入是否成功
  • multiset不会检测数据,因此可以插入重复数据
  • 总结:
    • 如果不允许插入重复数据可以利用set
    • 如果需要插入重复数据利用multiset

7、pair对组创建

功能描述:

  • 成对出现的数据,利用对组可以返回两个数据

两种创建方式:

  • pair<type, type> p ( value1, value2 );
  • pair<type, type> p = make_pair( value1, value2 );

实例:

#include <string>

//对组创建
void test01()
{
	pair<string, int> p(string("Tom"), 20);
	cout << "姓名: " <<  p.first << " 年龄: " << p.second << endl;

	pair<string, int> p2 = make_pair("Jerry", 10);
	cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

8、set容器排序

学习目标:

  • set容器默认排序规则为从小到大,掌握如何改变排序规则

主要技术点:

  • 利用仿函数,可以改变排序规则

示例一 set存放内置数据类型

#include <set>

class MyCompare 
{
public:
	bool operator()(int v1, int v2) {
		return v1 > v2;
	}
};
void test01() 
{    
	set<int> s1;
	s1.insert(10);
	s1.insert(40);
	s1.insert(20);
	s1.insert(30);
	s1.insert(50);

	//默认从小到大
	for (set<int>::iterator it = s1.begin(); it != s1.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	//指定排序规则
	set<int,MyCompare> s2;
	s2.insert(10);
	s2.insert(40);
	s2.insert(20);
	s2.insert(30);
	s2.insert(50);

	for (set<int, MyCompare>::iterator it = s2.begin(); it != s2.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

示例二 set存放自定义数据类型

#include <set>
#include <string>

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;

};
class comparePerson
{
public:
	bool operator()(const Person& p1, const Person &p2)
	{
		//按照年龄进行排序  降序
		return p1.m_Age > p2.m_Age;
	}
};

void test01()
{
	set<Person, comparePerson> s;

	Person p1("刘备", 23);
	Person p2("关羽", 27);
	Person p3("张飞", 25);
	Person p4("赵云", 21);

	s.insert(p1);
	s.insert(p2);
	s.insert(p3);
	s.insert(p4);

	for (set<Person, comparePerson>::iterator it = s.begin(); it != s.end(); it++)
	{
		cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl;
	}
}
int main() {

	test01();

	system("pause");

	return 0;
}

总结:

对于自定义数据类型,set必须指定排序规则才可以插入数据

map/multimap容器

1、基本概念

简介:

  • map中所有元素都是pair
  • pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值)
  • 所有元素都会根据元素的键值自动排序

本质:

  • map/multimap属于关联式容器,底层结构是用二叉树实现。

优点:

  • 可以根据key值快速找到value值

map和multimap区别

  • map不允许容器中有重复key值元素
  • multimap允许容器中有重复key值元素

2、构造赋值

构造:

  • map<T1, T2> mp; //map默认构造函数:
  • map(const map &mp); //拷贝构造函数

赋值:

  • map& operator=(const map &mp); //重载等号操作符

  • 示例:

    #include <map>
    
    void printMap(map<int,int>&m)
    {
    	for (map<int, int>::iterator it = m.begin(); it != m.end(); it++)
    	{
    		cout << "key = " << it->first << " value = " << it->second << endl;
    	}
    	cout << endl;
    }
    
    void test01()
    {
    	map<int,int>m; //默认构造
    	m.insert(pair<int, int>(1, 10));
    	m.insert(pair<int, int>(2, 20));
    	m.insert(pair<int, int>(3, 30));
    	printMap(m);
    
    	map<int, int>m2(m); //拷贝构造
    	printMap(m2);
    
    	map<int, int>m3;
    	m3 = m2; //赋值
    	printMap(m3);
    }
    
    int main() {
    
    	test01();
    
    	system("pause");
    
    	return 0;
    }
    

    总结:map中所有元素都是成对出现,插入数据时候要使用对组

3、大小交换

  • size(); //返回容器中元素的数目
  • empty(); //判断容器是否为空
  • swap(st); //交换两个集合容器

4、插入删除

  • insert(elem); //在容器中插入元素。
  • clear(); //清除所有元素
  • erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
  • erase(beg, end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
  • erase(key); //删除容器中值为key的元素。

5、查找统计

  • find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
  • count(key); //统计key的元素个数

6、map容器排序

学习目标:

  • map容器默认排序规则为 按照key值进行 从小到大排序,掌握如何改变排序规则

  • 利用仿函数,可以改变排序规则

STL常用算法

  • 算法主要是由头文件<algorithm> <functional> <numeric>组成。

  • <algorithm>是所有STL头文件中最大的一个,范围涉及到比较、 交换、查找、遍历操作、复制、修改等等

  • <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数

  • <functional>定义了一些模板类,用以声明函数对象。

1、遍历算法

for_each

  • for_each(iterator beg, iterator end, _func);

    // 遍历算法 遍历容器元素

    // beg 开始迭代器

    // end 结束迭代器

    // _func 函数或者函数对象

transform

  • transform(iterator beg1, iterator end1, iterator beg2, _func);

    //beg1 源容器开始迭代器

    //end1 源容器结束迭代器

    //beg2 目标容器开始迭代器

    //_func 函数或者函数对象

2、查找算法

find

  • find(iterator beg, iterator end, value);

    // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

    // beg 开始迭代器

    // end 结束迭代器

    // value 查找的元素

find_if

  • find_if(iterator beg, iterator end, _Pred);

    // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

    // beg 开始迭代器

    // end 结束迭代器

    // _Pred 函数或者谓词(返回bool类型的仿函数)

adjacent_find

  • 查找相邻重复元素

binary_search

  • bool binary_search(iterator beg, iterator end, value);

    // 查找指定的元素,查到 返回true 否则false

    // 注意: 在无序序列中不可用

    // beg 开始迭代器

    // end 结束迭代器

    // value 查找的元素

count

  • count(iterator beg, iterator end, value);

    // 统计元素出现次数

    // beg 开始迭代器

    // end 结束迭代器

    // value 统计的元素

count_if

  • count_if(iterator beg, iterator end, _Pred);

    // 按条件统计元素出现次数

    // beg 开始迭代器

    // end 结束迭代器

    // _Pred 谓词

3、排序算法

sort

  • sort(iterator beg, iterator end, _Pred);

    // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

    // beg 开始迭代器

    // end 结束迭代器

    // _Pred 谓词

merge

  • merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);

    // 容器元素合并,并存储到另一容器中

    // 注意: 两个容器必须是有序的

    // beg1 容器1开始迭代器
    // end1 容器1结束迭代器
    // beg2 容器2开始迭代器
    // end2 容器2结束迭代器
    // dest 目标容器开始迭代器

reverse

  • reverse(iterator beg, iterator end);

    // 反转指定范围的元素

    // beg 开始迭代器

    // end 结束迭代器

其他内容

auto

总述
auto的原理就是根据后面的值,来自己推测前面的类型是什么。

auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。

注意点
1.用auto声明的变量必须初始化(auto是根据后面的值来推测这个变量的类型,如果后面没有值,自然会报错)

2.函数和模板参数不能被声明为auto(原因同上)

3.因为auto是一个占位符,并不是一个他自己的类型,因此不能用于类型转换或其他一些操作,如sizeof和typeid

4.定义在一个auto序列的变量必须始终推导成同一类型
示例:

std::vector<std::string> ve;
std::vector<std::string>::iterator it = ve.begin();
//可以用auto代替,
auto it = ve.begin();
//此外,如果是可用迭代器的对象的话,还可以这样操作:
//auto在这里就是简单的替换了int类型。
int main(){
    vector<int>v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    for(auto i : v){
        cout<<i<<" ";
    }
    cout<<endl;
    return 0;
}

sstream使用简介

sstream即字符串流.
sstream有三种类:ostringstream:用于输出操作,istringstream:用于输入操作,stringstream:用于输入输出操作
其实我感觉只用第三个就够了-_-||

基本操作:
stringstream buff;
buff.str() 将buff里面的内容当做一个string返回
buff.str("....") 对buff赋值
buff.clear() 清空buff
可以使用流操作符>>和<<, 类比cin,cout.(要完美理解输入、输出流的含义)sstream数据总在左侧

建议在多次转换中重复使用同一个stringstream(而不是每次都创建一个新的对象),而在中间使用clear()函数清空.stringstream对象的构造和析构函数通常是非常耗费CPU时间的.

其实sstream最有用的还是在类型转换里面,把string转换成int,float等类型.

#include <iostream>
#include <sstream>
using namespace std;

int main()
{
    cout << "Hello world!" << endl;
	//line9--12:对buff1赋值,再赋给字符串st1
    ostringstream buff1;
    buff1<<"Sample1 #1";
     string st1=buff1.str();
     cout<<st1<<endl;

    istringstream buff2;
    buff2.str("Sample2 #2");
    string st2="ReWrite2";
    //buff2>>st2;
    getline(buff2,st2);
    cout<<st2<<endl;
	//line21--24:同上,只是改了个类型...
    stringstream buff3;
    buff3<<"Sample3 #3";
    string st3=buff3.str();
    cout<<st3<<endl;
	//line14--19和line26--30:都是用str()函数给buff赋值,然后写到字符串里.但是二者存在不同:14--19使用的getline,会读入一整行(当然包括了空格后面的内容),所以输出了完整的一句.而26--30是使用的流操作符.类比cin我们不难发现,在buff3>>st4时,buff3空格及后面的内容都被略掉了.
    buff3.clear();
    buff3.str("Sample4 #4");
    string st4="ReWirte4";
    buff3>>st4;
    cout<<st4<<endl;

//----------------------------------------------------
cout<<endl;
//----------------------------------------------------
	//当然最有实用价值的还是line36--40和line42--46,分别实现了string->int和string->double的类型转换.
    stringstream buff4;
    buff4.str("23333");
    int i;
    buff4>>i;
    cout<<i<<endl;

    buff4.clear();
    buff4.str("233.33");
    double j;
    buff4>>j;
    cout<<j<<endl;

    return 0;
}

位运算符

位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A = 1100 0011

image

实例

#include <iostream>
using namespace std;
int main()
{
   unsigned int a = 60;      // 60 = 0011 1100  
   unsigned int b = 13;      // 13 = 0000 1101
   int c = 0;           
   c = a & b;             // 12 = 0000 1100
   cout << "Line 1 - c 的值是 " << c << endl ;
   c = a | b;             // 61 = 0011 1101
   cout << "Line 2 - c 的值是 " << c << endl ;
   c = a ^ b;             // 49 = 0011 0001
   cout << "Line 3 - c 的值是 " << c << endl ;
   c = ~a;                // -61 = 1100 0011
   cout << "Line 4 - c 的值是 " << c << endl ;
   c = a << 2;            // 240 = 1111 0000
   cout << "Line 5 - c 的值是 " << c << endl ;
   c = a >> 2;            // 15 = 0000 1111
   cout << "Line 6 - c 的值是 " << c << endl ;
   return 0;
}

stoi、stol、stoll函数用法

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值

堆序性

  • 小根堆

    每个父结点元素都要小于他的子节点元素

  • 大根堆

    每个父结点元素都要大于他的子节点元素

堆的存储

  • 按照层序遍历的顺序来给结点编号,把元素对应给到数组

    image

    结点下标位i时,左右子节点下标分别为2i+1、2i+2。

基本操作

  • 上滤

    节点向上调整的操作为上滤O(logn),常用于将元素插入到堆尾部操作

    以大跟堆为例,如果最后一个元素破坏了堆序性,那么该元素和其父结点比较,如果大于父结点则进行交换,重复操作到符合堆序性为止

  • 下滤

    根节点向下调整的操作为下滤O(logn)

    以大跟堆为例,如果一个元素破坏了堆序性,那么该元素和其最大的子节点比较,如果小于子节点则进行交换,重复操作到符合堆序性为止

建堆

  • 自堆向下O(nlogn)

    将新元素插入到堆的正因为,然后进行上滤操作

  • 自下向上O(n)

    先把元素调整成堆,对每个父结点进行下滤操作,从倒数第二排开始,对每个父结点进行下滤操作

优先队列

​ 可以用小根堆实现

  1. 基本操作

    • 插入队列-直接上滤O(logn)

    • 弹出最小元素O(logn)

      将根节点弹出,将最后一个元素放到根节点,进行下滤操作

  2. 应用

    小根堆弹出,可以实现堆排序,复杂的还可以优化

    可以用大根堆,直排序完接就是正序了O(nlogn)。

优先级队列

1、基本概念

​ 之前已经提到了队列(queue),队列是一种先进先出(First in First out,FIFO)的数据类型。每次元素的入队都只能添加到队列尾部,出队时从队列头部开始出。

优先级队列(priority_queue)其实,不满足先进先出的条件,更像是数据类型中的“堆”。优先级队列每次出队的元素是队列中优先级最高的那个元素,而不是队首的元素。这个优先级可以通过元素的大小等进行定义。比如定义元素越大优先级越高,那么每次出队,都是将当前队列中最大的那个元素出队。个人感觉这就是所谓“优先级”的定义。

​ 现在看优先级队列是不是就是“堆”了,如果最大的元素优先级最高,那么每次出队的就是当前队列中最大的元素,那么队列实际就相当于一个大顶堆,每次将堆根节点元素弹出,重新维护大顶堆,就可以实现一个优先级队列。

2、优先级队列的定义

​ C++中,使用优先级队列需要包含头文件,优先级队列的定义如下:

一个优先队列声明的基本格式是:
priority_queue<结构类型> 队列名;
比如:

priority_queue <int> i;
priority_queue <double> d;

不过,我们最为常用的是这几种:

priority_queue <node> q;
//node是一个结构体
//结构体里重载了‘<’小于符号
priority_queue <int,vector<int>,greater<int> > q;
//不需要#include<vector>头文件
//注意后面两个“>”不要写在一起,“>>”是右移运算符
priority_queue <int,vector<int>,less<int> >q;
priority_queue<typename, container, functional>
  • typename是数据的类型;

  • container是容器类型,可以是vector,queue等用数组实现的容器,不能是list,默认可以用vector;

  • functional是比较的方式,默认是大顶堆(就是元素值越大,优先级越高);如果使用C++基本数据类型,可以直接使用自带的less和greater这两个仿函数(默认使用的是less,就是构造大顶堆,元素小于当前节点时下沉)。使用自定义的数据类型的时候,可以重写比较函数,也可以进行运算符重载(less重载小于“<”运算符,构造大顶堆;greater重载大于“>”运算符,构造小顶堆)。
    定义一个优先级队列的示例如下:

    //构造一个大顶堆,堆中小于当前节点的元素需要下沉,因此使用less
    priority_queue<int, vector<int>, less<int>> priQueMaxFirst;
     
    //构造一个小顶堆,堆中大于当前节点的元素需要下沉,因此使用greater
    priority_queue<string, vector<string>, greater<string>> priQueMinFirst;
    

3、通过重写仿函数来支持自定义数据类型

仿函数是通过重载“()”运算符来模拟函数操作的类

​ 先自定义一个类Data,将id作为该类的关键字,进行比较,重写仿函数

//自定义数据类型,Data类
class Data
{
public:
	Data(int i, int d) :id(i), data(d) {}
	~Data() {}
	int getId() { return id; }
	int getData() { return data; }
private:
	int id;
	int data;
};
//重写仿函数,完成less的功能,也可以用class定义类,此时需要将运算符重载函数设为public
//结构体struct中默认是访问类型是public
struct cmp    
{
	bool operator() ( Data &a, Data &b) {
		return a.getId() < b.getId();
	}
};
 
int main(void){
    priority_queue<Data, vector<Data>, cmp > priQueMaxFirst;//该优先级队列维护一个大顶堆,因此最大的元素最先出队
    ...//一系列操作
    ...
    return 0;
}
 

4、通过运算符重载来支持自定义比较函数

运算符重载的话,由于是重载双目运算符,因此需要使用友元函数,我们在类内声明友元函数,在类外实现友元函数,如下:

//自定义数据类型,Data类
class Data
{
public:
	Data(int i, int d) :id(i), data(d) {}
	~Data() {}
	int getId() { return id; }
	int getData() { return data; }
	friend bool operator < (const Data &a, const Data &b);//运算符重载,友元函数可以访问类的私有成员
private:
	int id;
	int data;
};
//重载“<”运算符,仿函数less中需要使用
bool operator < (const Data &a, const Data &b) {
	return a.id < b.id;
}
 
int main(void){
    priority_queue<Data, vector<Data>, less<Data> > priQueMaxFirst;//该优先级队列维护一个大顶堆,因此最大的元素最先出队
    ...//一系列操作
    ...
    return 0;
}
 

5、优先级队列的基本操作

优先级队列的基本操作与普通队列类似,不同的是每次获得队内的元素是优先级最高的元素(要从堆的顶部开始),因此使用的是top()方法,而不是front()方法。如下:

  1. push() :入队。向队列添加一个元素,无返回值;
  2. pop() :将队列中优先级最高的元素出队。将队列中优先级最高的元素删除(出队),无返回值;
  3. top() :获得队列优先级最高的元素。此函数返回值为队列中优先级最高的元素,常与pop()函数一起,先通过top()获得队列中优先级最高的元素,然后将其从队列中删除;
  4. size() :获得队列大小。此函数返回队列的大小,返回值是“size_t”类型的数据,“size_t”是“unsigned int”的别名。
  5. empty() :判断队列是否为空。此函数返回队列是否为空,返回值是bool类型。队列空:返回true;不空:返回false。

取整

在数学计算中有三种常用的取整操作:向上取整、向下取整、四舍五入取整,在 C++ 中分别通过 ceil()、floor()、round() 三个函数来实现,下面就介绍一些这三个函数以及取整操作。

sort的使用

  1. 形参写成了const引用的形式

  2. 函数更改为静态函数static

关于1:当引用作为形参,函数调用时也可以看成将传递的实参绑定给它,这样我们在函数体内对这个引用做的一切操作都有可能影响到函数传递的实参。如果我们希望参数在函数体内是只读的,所以当我们加了引用有希望参数是只读的就必须加 const。
为什么不直接值传递呢? 确实,但是当参数是类对象时值传递就有了一个问题,那就是性能可能会大受影响。我们知道值传递实际就是向函数拷贝一份副本来使用,那么对于一些复杂的类,尤其是 string 这样每一次拷贝可能消耗很多的时间,那么通过引用传参就很有必要了。
总的来说因为我想提高类对象传参时的性能,所以要用引用,因为用了引用我又希望它只读所以我用了const。

关于2:在(非静态成员)函数的返回类型前加上关键字static,函数就被定义成为静态函数。普通 函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。因此定义静态函数有以下好处:
  <1> 其他文件中可以定义相同名字的函数,不会发生冲突。
  <2> 静态函数不能被其他文件所用。

有一种定义cmp的方式是直接在sort里面定义(courses是二维数组,按c[1]从小到大排)

sort(courses.begin(), courses.end(), [](const auto& c0, const auto& c1) {return c0[1] < c1[1];});

也可以传入一个x,返回和x操作后的排序,用到了[lambda表达式了]([C++lambda表达式_c++ lamba表达式简写语法 直接返回变量_Cbdl的博客-CSDN博客](https://blog.csdn.net/weixin_62671257/article/details/127813581?ops_request_misc={"request_id"%3A"169607524916800222840607"%2C"scm"%3A"20140713.130102334.pc_all."}&request_id=169607524916800222840607&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-127813581-null-null.142v94insert_down28v1&utm_term=C%2B%2B lambda表达式的&spm=1018.2226.3001.4187))

sort(arr.begin(), arr.end(), [x](int a, int b) -> bool {
            return abs(a - x) < abs(b - x) || abs(a - x) == abs(b - x) && a < b;
        });

iota()函数

逐个加一赋值

iota函数对一个范围数据进行赋值:

template <class ForwardIterator, class T>
  void iota (ForwardIterator first, ForwardIterator last, T val)
{
  while (first!=last) {
    *first = val;
    ++first;
    ++val;
  }
}

用法:

// iota example
#include <iostream>     // std::cout
#include <numeric>      // std::iota

int main () {
  int numbers[10];

  std::iota (numbers,numbers+10,100);

  std::cout << "numbers:";
  for (int& i:numbers) std::cout << ' ' << i;
  std::cout << '\n';

  return 0;
}

阻断scanf,printf

ios_sync_with_stdio(fasle);

二分查找函数

  • 关于lower_bound( )和upper_bound( )的常见用法-CSDN博客

    ower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。

    在从小到大的排序数组中,

    lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

    upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

    在从大到小的排序数组中,重载lower_bound()和upper_bound()

    lower_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

    upper_bound( begin,end,num,greater() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=100000+10;
    const int INF=2*int(1e9)+10;
    #define LL long long
    int cmd(int a,int b){
    	return a>b;
    }
    int main(){
    	int num[6]={1,2,4,7,15,34}; 
    	sort(num,num+6);                           //按从小到大排序 
    	int pos1=lower_bound(num,num+6,7)-num;    //返回数组中第一个大于或等于被查数的值 
    	int pos2=upper_bound(num,num+6,7)-num;    //返回数组中第一个大于被查数的值
    	cout<<pos1<<" "<<num[pos1]<<endl;
    	cout<<pos2<<" "<<num[pos2]<<endl;
    	sort(num,num+6,cmd);                      //按从大到小排序
    	int pos3=lower_bound(num,num+6,7,greater<int>())-num;  //返回数组中第一个小于或等于被查数的值 
    	int pos4=upper_bound(num,num+6,7,greater<int>())-num;  //返回数组中第一个小于被查数的值 
    	cout<<pos3<<" "<<num[pos3]<<endl;
    	cout<<pos4<<" "<<num[pos4]<<endl;
    	return 0;	
    } 
    

memset() 使用方法

memset()函数的用法详解-CSDN博客

单调栈

算法数据结构——关于单调栈(Monotone Stack)的详细讲解及应用案例-CSDN博客

[C++单调栈_给定一个长度为n的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1-CSDN博客](https://blog.csdn.net/weixin_51522849/article/details/120765756?ops_request_misc={"request_id"%3A"170930691316800186569182"%2C"scm"%3A"20140713.130102334.."}&request_id=170930691316800186569182&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-120765756-null-null.142v99pc_search_result_base8&utm_term=单调栈算法 c%2B%2B&spm=1018.2226.3001.4187)

筛选法求质数(素数)

#include<bits/stdc++.h>
using namespace std;

bool p[1005];
int n;

int main() {
	cin >> n;
	//筛到n的平方根即可 
	for (int i = 2; i <= n / i; i++) {
		if (p[i] == 0) {
			//将后面的倍数都排除掉 
			for (int j = 2; i * j <= n; j++) {
				p[i * j] = 1;
			}
		}
	}
	for (int i = 2; i <= n; i++) {
		if (p[i] == 0) {
			cout << i << endl;
		}
	}
	return 0;
}

快速幂

快速幂算法(全网最详细地带你从零开始一步一步优化)-CSDN博客

long long fastPower(long long base, long long power) {
    long long result = 1;
    while (power > 0) {
        if (power & 1) {//此处等价于if(power%2==1)
            result = result * base % 1000;
        }
        power >>= 1;//此处等价于power=power/2
        base = (base * base) % 1000;
    }
    return result;
}
posted @ 2025-03-21 23:11  韩熙隐ario  阅读(30)  评论(0)    收藏  举报