标准库
iostream
// cin读到空格前面
cin << n;
// 读取⼀⾏的字符串,包括空格。
// 使用getline前,应该用getchar()把getline()前面的换行符读取了,否则getline()会读成前面的换行符。
getline(cin, s1);
cstdio
#include <cstdio> // 等价于#include <stdio.h>
scanf("%d%c%s", &a, &c, str);
// 输入1 a bcd
// 输出1 a
// 对于正常格式符(如%d、%s)以空格、tab、换行等为结束标志;%c可以读如空格、换行。
// 当数据比较大时,cin可能会超时,要改成scanf
getchar();
climits
#include <climits>
// INT_MAX
// INT_MIN
cstdlib
#include <cstdlib>
// abs(int x)
cmath
#include <cmath> // 等价于#include <math.h>
// fabs(double x)
// 取绝对值
double db = -13.31;
fabs(db)
// floor(double x)、ceil(double y)
// 向下取整或向上取整,返回值为double类型
floor(db)
ceil(db)
// pow(double r, double p)
// 返回r^p
pow(2.0, 3.0)
// sqrt(double x)
// 返回算术平方根
sqrt(2.0)
// log(double x)
// 返回log10x(没有对任意底数取对数的函数,需要通过换底公式实现)
log(1.0)
// sin(double x)、cos(double x)、tan(double x)
// asin(double x)、acos(double x)、atan(double x)
// round(double x)
// 四舍五入
cstring
#include <cstring> // 等价于#include <string.h>
memset(数组名,值,sizeof(数组名))
// 建议只使用memset赋初值0或-1(memset按字节赋值,对每个字节赋相同的值)。
void *memset(void *s, int c, size_t count)
{
char *xs = s;// xs为指向char类型的指针。
while (count--)
*xs++ = c;
return s;
}
// 如果用memset(a,1,20),就是对a指向的内存的20个字节进行赋值,每个都用数1去填充,转为二进制后,1就是00000001,占一个字节。一个int元素是4字节,合一起是0000 0001,0000 0001,0000 0001,0000 0001,转化成十六进制就是0x01010101,就等于16843009,就完成了对一个int元素的赋值了。
strlen(字符数组)
// 返回字符数组中第一个\0前的字符个数。
strcmp(字符数组1, 字符数组2)
// 返回两个字符串大小的比较几个,按字典序。
strcat(字符数组1, 字符数组2)
// 把字符数组2接到字符数组1后面。
sscanf(str, "%d", &n)
// 把str以%d的格式写入n,➡️
sprintf(str, "%d", n)
// 把n以%d的格式写入str,⬅️
char str[100] = "2048:3.14,hello";
sscanf(str, "%d:%lf,%s", &n, &db, str2);
str.c_str()
// 将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址。
sstream
// 把字符转成数字
string line;
getline(cin, line);
stringstream ssin(line);
int v;
ssin >> v;
STL标准模板库
string
#include <string>; // 和string.h不一样
// 字符串拼接
// 1、+
// 2、append()
string s1 = "hello";
s1.append(" world");//hello world
s1.append(" aaa,aaaa", 4);//hello world aaa//只拼接该字符串的前n个
string s2 = " abcdedg"
s1.append(s2, 0, 3);//hello world aaa ab//只拼接该字符串的从哪个位置开始,几个字符
// 长度
s1.size()
s1.empty()
// 插入
s1.insert(1, "aaa");// haaaello
// 删除
s1.erase(1, 3);// hello
s1.clear();
// 截取(开始位置,截取长度)
s1.substr(1, 3);// ell
s1.substr(1);// ello 从1开始的子串
// 查找
// 1、find():找到返回第一次出现的位置;否则,返回string::nops
// string::nops是一个常数,本身值为-1;因为是unsigned_int类型,可以认为是unsigned_int的最大值。
s1.find("he");// 0
// 2、rfind():从右往左查(返回的是下标)
s1.rfind("he");// 0
// 3、replace(start, n, str):start起始位置,n几个字符,替换的字符串
vector
- 需要高效的随机存取,而不在乎插入和删除的效率,使用vector。
#include <vector>;
// 构造
// 1、
vector<int> v4 = {1, 2, 3};
// 2、定义长度为n
vector<int> v(7);
// 3、定义长度为n,值为elem
vector<int> v(2, 100);
// 判断是否为空
v.empty()
// 容量
v.capacity()
// 容器中元素个数
// 系统为某个程序分配空间时,所需时间与空间大小无关,与申请次数有关。
v.size()
// 重新指定大小
v.resize(15);// 变长,用默认值填充;变短,删除。
v.resize(2, 100);// 变长,用elem填充;变短,删除。
// 交换
v1.swap(v2);
// 利用swap收缩内存
vector<int>(v).swap(v);
// 预留空间(预留位置不初始化,元素不可访问)
vector<int> v2;
v2.reserve(100000);
cout << v2.size() << endl; //打印出来的是当前真实包含元素个数
cout << v2.capacity() << endl; //100000,打印出来的是预留的元素个数
// 插入
v.push_back(1);
v.insert(0, 2);// 往0位置插入元素2
v.insert(v.begin(), 2);// 往迭代器处插入元素2
// 删除
v.pop_back();// 删除最后一个元素
v.erase(v.begin());// 删除迭代器处的元素
v.erase(2, 6);// 删除[2, 6)的元素
v.clear();// 清空
// 访问
// 1、通过下标访问
v[0]
// 2、通过at访问
v.at(0)
// 3、返回容器中第一个数据元素
v.front()
// 4、返回容器中最后一个数据元素
v.back()
// 遍历
// 1、下标
for(int i = 0; i < v.size(); i++) cout << v[i] << endl;
// 2、迭代器
for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
cout << *it << endl;
for(vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); it++)
for(auto it = v.begin(); it != v.end(); it ++)
// 3、for range
for(auto x : v) cout << x << endl;
bitset
bitset<10000> s;// <>里是位数
// 第k位
s[0] // 最低位
s[9999] // 最高位
// 有多少位为1
s.count()
// 是否至少有一位为1
s.any()
// 是否所有位都为0
s.none()
s.set()// 把所有位变成1
s.set(k, v)// 把第k位改成v
s.reset()// 把所有位变成0
s.reset(k)// 把第k位改成0
s.flip()// 把所有位取反
s.flip(k)// 把第k位取反
set
- 自动排序,去重。
- 底层用红黑树实现,O(logN)。
#include <set>
set<int> st;
// 返回元素个数
st.size()
// 判断容器是否为空
st.empty()
// 交换
st1.swap(st);
// 插入
st.insert(1);
// 删除
st.erase(st.begin());// 删除迭代器位置的元素
st.erase(st.begin(), st.end());// 删除迭代器[begin, end)的元素
st.erase(10);// 删除值为10的元素
st.clear();
// 查找
st.find(0);// 查找0,存在返回迭代器;不存在返回st.end();
lower_bound(x)// 返回>=x的左边界的迭代器
upper_bound(x)//返回<=x的左边界的迭代器
// 统计
st.count(0);// 统计元素为0的个数
// 排序
// 修改排序规则之
// 1、数据类型为内置数据类型
class MyCompare
{
public:
bool operator()(int a, int b)
{
return a > b;
}
}
int main()
{
// 改成降序排列
set<int, MyCompare> st;
}
// 2、数据类型为自定义数据类型
class PersonCompare
{
public:
bool operator(const Person &p1, const Person &p2)
{
return p1.m_Age > p2.m_Age;
}
}
int main()
{
set<Person, PersonCompare> s;
}
// 遍历
for(set<int>::iterator it = st.begin(); it != st.end(); it++){
...
}
multiset
- 自动排序,不去重(元素不唯一)。
unordered_set
- 只处理去重,不处理排序。
- 比set快。
- 底层用hash表实现,O(1)。
- 不支持upper_bound、lower_bound以及一系列和排序有关的操作
unordered_multiset
map
- 按照键值自动排序,去重。
- 底层用红黑树实现,O(logN)。
// 构造
// 1、默认构造
map<int, int> mp;
// 2、拷贝构造
map<int, int>mp2(mp);
// 赋值
mp2 = mp;
// 返回元素个数
mp.size()
// 判断容器是否为空
mp.empty()
// 交换
mp2.swap(mp);
// 插入
// 1、insert
mp.insert(pair<int, int>(0, 1));
mp.insert(make_pair(0, 1));
mp.insert(map<int, int>::value_type(0, 1));
// 2、[]
mp[0] = 1;
// 3、emplace:用法和insert差不多c++11,效率比insert()好
// 删除
mp.erase(mp.begin());// 删除迭代器的元素
mp.erase(mp.begin(), mp.end());// 删除[begin, end)的元素
mp.erase(0);// 删除key为0的元素
mp.clear();
// 查找
// 1、find
mp.find(0);// 查找key=0是否存在,存在返回迭代器;不存在返回map.end()
// 2、lower_bound(val)
mp.lower_bound(1);// 返回第一个key>=val的迭代器
// 3、upper_bound(val)
mp.upper_bound(1);// 返回第一个key>val的迭代器
// 统计
mp.count(0);// 统计key=0的元素个数
// 排序(类似set)
// 遍历
// 1、迭代器
for(map<int, int>::iterator it = mp.begin(); it != mp.end(); it++)
{
// cout << (*it).first << (*it).second << endl;
cout << it->first << it->second << endl;
}
// 2、range for(c++11以上)
for(auto it : mp){
cout << it.first << ", " << it.second << endl;
}
// 3、(c++17以上)
for(auto [key, val] : mp){
cout << key << ", " << val << endl;
}
multimap
- 按照键值自动排序,不去重(一个键可以对应多个值)。
unordered_map
- 只处理映射,不处理排序。
- 比map快。
- 底层用hash表实现,O(1)。
unordered_multimap
stack
#include <stack>
// 构造函数
// 1、默认构造
stack<int> s;
// 2、拷贝构造
// 赋值
stack<int> s1;
s1 = s;
// 插入
s.push(10);
// 删除
s.pop();
// 返回栈顶元素
s.top();
// 判断是否为空
s.empty()
// 返回栈的大小
s.size()
// 遍历
while(!s.empty()){
...
}
queue
#include <queue>
queue<int> que;
// 插入
que.push(1);
// 删除
que.pop();
// 返回最后一个元素
que.back()
// 返回第一个元素
que.front()
// 判断是否为空
que.empty()
// 清空
que = queue<int>();
// 返回队列的大小
que.size()
// 遍历
while(!q.empty()){
...
}
priority_queue
- 堆。
- 默认大根堆。
#include <queue>
// 定义
priority_queue<int> pq;
// 添加元素
pq.push(1);
// 获取堆顶元素
pq.top();
// 弹出堆顶元素,无返回值
pq.pop();
// 检测堆是否为空
pq.empty()
// 获取堆内元素个数
pq.size()
// 优先级设置
// 1、基本数据类型
// 默认数字大的优先级越高(char型是字典序大的优先级越高)
priority_queue<int> pq;
// 等价于(第二个参数:承载底层数据结构堆的容器;第三个参数:对第一个参数的比较类,less<int>表示数字大的优先级大,greater<int>表示数字小的优先级大)
priority_queue<int, vector<int>, less<int> > pq;
// 2、自定义数据类型
// 大根堆重载自定义数据类型的<,小根堆重载自定义数据类型的>。
// STL中绝大部分涉及排序的容器都使用到了小于号,最常见的如sort()函数,所以要重载小于号。
// https://www.acwing.com/blog/content/8158/
deque
- 双端数组
#include <deque>;
// 大多操作类似vector
// 插入
d.push_back(1);
d.push_front(1);
// 删除
d.pop_back();
d.pop_front();
// 遍历
// 只读对应只读迭代器
void printDeque(const deque<int>&d)
{
for(deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
{
cout << *it << endl;
}
}
list
- 需要大量的插入删除,而不关心随机存取,使用list。
#include <list>
// 构造函数
// 1、默认构造
list<int> lst;
// 2、区间构造
list<int>l1(lst.begin(), lst.end())
// 3、(n个elem)
list<int>l2(2, 10);
// 4、拷贝构造
list<int>l3(lst);
// 赋值
// 1、=
li = lst;
// 2、assign
l1.assign(lst.begin(), lst.end());// 拷贝[begin, end)
l1.assign(2, 10);// n个elem
// 交换
l1.swap(lst);
// 元素个数
lst.size()
// 判断是否为空
lst.empty()
lst.resize(10);
lst.resize(10, 2);
// 插入
lst.push_back(10);
lst.push_front(20);
lst.insert(0, 2);// 往0位置插入元素2
lst.insert(0, 2, 10);// 往0位置插入2个10
lst.insert(0, l1.begin(), l1.end());// 往0位置插入迭代器[begin, end)的数据
// 删除
lst.pop_back();
lst.pop_front();
lst.erase(lst.begin());// 删除迭代器处的元素
lst.erase(lst.begin(), lst.begin()+2);// 删除迭代器[begin, end)的数据
lst.remove(0);// 删除所有等于0的元素
lst.clear();
// 返回第一个元素
lst.front()
// 返回最后一个元素
lst.back()
// 反转
lst.reverse();
// 排序
lst.sort();
// 遍历
for(list<int>::iterator it = lst.begin(); it != lst.end(); it++){
...
}
pair
#include <utility>
// #include <map>中也有
// 构造
// 1、
pair<int, int> p = {0, 1};
// 2、make_pair
pair<int, int> p = make_pair(0, 1);
// 访问
p.first
p.second
// 支持排序,以first的字典序排序
algorithm
// max(double x, double y)
// min(double x, double y)
// pre_permutation(start, end)
// next_permutation(start, end):求出全排列中的下一个序列
int main(){
int nums[3] = {1, 2, 3};
do{
cout << nums[0] << " " << nums[1] << " " << nums[2] << endl;
}while(next_permutation(nums, nums+3));
/*
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*/
do{
cout << nums[0] << " " << nums[1] << " " << nums[2] << endl;
}while(next_permutation(nums, nums+2));
/*
1 2 3
2 1 3
*/
// next_permutation(nums, nums+n)函数是对数组nums中的前n个元素进行全排列,同时改变nums数组的值。返回布尔值。
// 在使用时需要对欲排列数组按升序排列,否则只能找出该序列后的全排列数。
int nums[3] = {2, 3, 1};
do{
cout << nums[0] << " " << nums[1] << " " << nums[2] << endl;
}while(next_permutation(nums, nums+3));
/*
2 3 1
3 1 2
3 2 1
*/
}
next_permutation(nums)
遍历算法
// for_each(begin, end, func):begin起始迭代器,end结束迭代器,func处理函数or仿函数
void printFun(int val)
{
cout << val << endl;
}
int main()
{
vector<int> vi;
for_each(vi.begin(), vi.end(), printFun);
}
// transform(beg1, end1, beg2, func):beg1源容器起始迭代器,end1源容器结束迭代器,beg2目标容器起始迭代器,func处理函数or仿函数
// 目标容器需要提前开辟空间。
class Trans
{
public:
int operator()(int v)
{
return v + 100;
}
}
int main()
{
...
vector<int> target;
target.resize(v.size());
transform(v.begin(), v.end(), target.begin(), Trans());
}
查找算法
// find(begin, end, value):查找指定元素是否存在,返回迭代器;begin起始迭代器,end结束迭代器,value查找的元素
// 1、内置数据类型
vector<int>::iterator it = find(v.begin(), v.end(), 5);
// 2、自定义数据类型:需要重载类里面的==,让find知道如何比较
vector<Person>::iterator it = find(v.begin(), v.end(), p);
// find_if(begin, end, pred):条件查找,返回迭代器;begin起始迭代器,end结束迭代器,pred函数或仿函数
// 1、内置数据类型
class GreaterFive
{
public:
bool operator()(int val)
{
return val > 5;
}
}
int main()
{
...
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
}
// 2、自定义数据类型
class GreaterTwelve
{
public:
bool operator()(Person &p)
{
return p.m_Age > 12;
}
}
int main()
{
...
vector<Person>::iterator it = find_if(v.begin(), v.end(), GreaterTwelve());
}
// adjacent_find(begin, end):查找相邻重复元素,返回相邻元素的第一个位置的迭代器;begin起始迭代器,end结束迭代器
vector<int>::iterator it = adjacent_find(v.begin(), v.end());
// binary_search(begin, end, value):查找指定元素是否存在,返回布尔值;begin起始迭代器,end结束迭代器,value查找的值
// 只能在有序序列中使用(如果是无序序列,结果未知)
// count(begin, end, value):统计元素出现次数;begin起始迭代器,end结束迭代器,value查找的值
// 1、内置数据类型
int num = count(v.begin(), v.end(), 40);
// 2、自定义数据类型
class Person
{
public:
bool operator==(const Person &p)
{
return p.m_Age == this->m_Age;
}
}
int main()
{
...
int num = count(v.begin(), v.end(), p);
}
// count_if(begin, end, pred):按条件统计元素出现个数;begin起始迭代器,end结束迭代器,pred函数或仿函数
// 1、内置数据类型
class Greater20
{
public:
bool operator()(int val)
{
return val > 20;
}
}
int main()
{
...
int num = count_if(v.begin(), v.end(), Greater20());
}
// 2、自定义数据类型
class PersonAgeGreater20
{
public:
bool operator()(const Person &p)
{
return p.m_Age > 20;
}
}
int main()
{
...
int num = count_if(v.begin(), v.end(), PersonAgeGreater20());
}
// lower_bound(begin, end, val):寻找数组或容器[begin, end)返回内第一个值>=val的元素的位置,返回指针或迭代器。
// upper_bound(begin, end, val):寻找数组或容器[begin, end)返回内第一个值>val的元素的位置,返回指针或迭代器。
int a[10] = {1, 2, 2, 3, 3, 3, 5, 5, 5, 5};
int* lowerPos = lower_bound(a, a+10, -1);
int* upperPos = upper_bound(a, a+10, -1);
// 若只查下标而不使用指针,则直接令返回值减去数组收地址即可
cout << lowerPos - a << endl;// 0
cout << upperPos - a << endl;// 0
排序算法
// sort(begin, end, pred):begin起始迭代器,end结束迭代器,pred函数
// 1、默认数据类型
// 默认升序排列
sort(v.begin(), v.end());
// 改成降序排列(从大到小)
sort(v.begin(), v.end(), greater<int>());
bool cmp(int a, int b){
return a > b;
}
sort(a, a+4, cmp);
// 2、自定义数据类型:类似(可以重载比较运算符?仿函数)
// 二级比较
bool cmp(node a, node b){
if(a.x != b.x) return a.x > b.x;
else return a.y > b.y;
}
// random_shuffle(begin, end):洗牌;begin起始迭代器,end结束迭代器
random_shuffle(v.begin(), v.end());
// merge(begin1, end1, begin2, end2, dest):归并;begin1容器1起始迭代器,end1容器1结束迭代器,begin2容器2起始迭代器,end2容器2结束迭代器,dest目标容器开始迭代器
// 容器必须都是有序序列。
// 提前给目标容器分配内存
dest.resize(v1.size()+v2.size());
// reverse(begin, end):反转;begin起始迭代器,end结束迭代器
reverse(v.begin(), v.end());
// 将重复的元素移动到数组的末尾,最后再将迭代器指向第一个重复元素的下标。
unique(v.begin(), v.end());
拷贝和替换算法
// copy(begin, end, dest):将源容器拷贝到目标容器;begin源容器起始迭代器,end源容器结束迭代器,dest目标起始迭代器
// 记得开辟空间
v2.resize(v1.size());
copy(v1.begin(), v1.end(), v2.begin());
// replace(begin, end, oldValue, newValue):begin起始迭代器,end结束迭代器,oldValue旧元素,newValue新元素
replace(v.begin(), v.end(), 20, 2000);
// replace_if(begin, end, pred, newValue):begin起始迭代器,end结束迭代器,pred函数,newValue新元素
replace_if(v.begin(), v.end(), Greater20(), 2000);
// swap(container1, container2)
swap(v1, v2);
算术生成算法
#include <numeric>
// accumulate(begin, end, value):计算容器元素累计总和;begin起始迭代器,end结束迭代器,value起始值
int total = accumulate(v.begin(), v.end(), 0);
// fill(begin, end, value):向容器中填充元素;begin起始迭代器,end结束迭代器,value填充的值
// 一般用于后期填充
fill(v.begin(), v.end(), 100);
集合算法
// set_intersection(begin1, end1, begin2, end2, dest):求交集
// 容器必须都是有序序列
// 目标容器需要提前开辟空间
target.resize(min(v1.size(), v2.size()));
vector<int>::iterator itEnd = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), target.begin())// 交集的最后位置
for_each(target.begin(), itEnd, myPrint);// 1 2 3
// for_each(target.begin(), target.end(), myPrint);// 1 2 3 0 0 0
// set_union(begin1, end1, begin2, end2, dest):求并集
// set_difference(begin1, end1, begin2, end2, dest):求差集
变量名
- 第一个字符:字母or下划线。
- 之后:字母or下划线or数字。
区分大小写。
赋值
// c++可以连续使用赋值运算符,从右往左赋值。
int a = b = c = 1;
常量
// #define:没有分号。
// 1、无法指明类型
// 2、作用域是全局
// 3、只做字符替换
// const
const int months = 12;
// 1、指明类型
// 2、可以限定作用域:需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。
// 3、用于更复杂的类型
// 错误!在声明常量时没有提供值,则该常量的值将是不确定的,且无法修改。
const int months;
months = 12;
// 常量指针:让指针指向一个常量对象,防止使用该指针来修改所指向的值。
int a = 20;
const int * p = &a;
*p = 10;// error
a = 10;// 可
p = &b;// 可
// 结构体中,防止误操作
void printStudent(const Student * s)
{
s->age = 1;// error
cout << s->age << endl;
}
// 指针常量:将指针本身声明为常量,防止修改指针指向的位置。
int * const p = &a;
*p = 20;// 可
p = &b;// error
// 指针指向的值和指针指向的位置都不能修改。
const int * const p = &a;
基本数据类型
long long型被赋大于2^31-1的初值,需要在初值后面加上LL。
整数
- 10^9 以内或者32位整数,用int;10^18以内或者64位整数,用long long型。
浮点数
- 都用double。
浮点数的比较
c/c++中,==是完全相等才为true。(浮点数3.14在计算机中可能存储为3.140000001也可能是3.139999999,导致产生误差,需引入极小数eps来消除误差)
const double eps = 1e-8;
// ==:|a-b| < eps
#define Equ(a, b) ((fabs((a)-(b))<(eps)))
// >:a > b + eps
#define More(a, b) (((a)-(b))>(eps))
// <:a < b - eps
#define Less(a, b) (((a)-(b)<(-eps)))
// >=:a > b - eps
#define MoreEqu(a, b) (((a)-(b))>(-eps))
// <=:a < b + eps
#define LessEqu(a, b) (((a)-(b))<(eps))
const double pi = acos(-1.0);
数组
// 一维数组的定义
// 只有在定义数组时才能使用初始化,之后就不能用了;也不能将一个数组赋给另一个数组。
int a[10];
int b[10] = {1, 2, 3, 4, 5}; // 后面未定义的为0或编译器初值(随机数)。
int c[10] = {0}; // 整个数组都赋初始值0。
int d[10] = {}; // 整个数组都赋初始值0。
// 二维数组的定义
int a[5][6]; //定义一个5x6的数组。
// 如果数组大小较大(10^6),需要定义在主函数外面,否则会使程序异常退出。(函数内部申请的局部变量来自系统栈,允许申请的空间较小;函数外部申请的全局变量来自静态存储区,允许申请的空间较大)
// memset(数组名,值,sizeof(数组名))
// string.h
// 字符数组
char str[15] = {'g', 'o', 'o', 'd'};
char str[15] = 'good';// 仅限初始化时候这么赋值。
// 数组作为函数的参数
// 一维数组无需填写长度,二维数组的第二维需要填写长度。
// 在函数内对数组元素的修改就等于对原数组元素的修改。
void fn(int a[], int b[][5]){
}
结构体
// 结构体定义
struct node{ // 结构体类型名
...
node n; // 不能定义node型变量
node* next; // 可以定义node型指针变量
}s, *p, stu[1000]; // 结构体变量or数组
// 访问普通变量
s.next
// 访问指针变量
(*p).next
p->next
// 结构体初始化
struct studentInfo{
int id;
char gender;
// 构造函数:用来初始化结构体,名字和结构体类型名相同
// 默认的构造函数(看不见
studentInfo(){}
// 自定义
studentInfo(int _id, char _gender){
id = _id;
gender = _gender;
}
// 自定义(简化版
studentInfo(int _id, char _gender): id(_id), gender(_gender){}
// 自定义(只初始化某些参数
studentInfo(int _id){
id = _id;
}
}
studentInfo stu = studentInfo(10086, 'm');
值传递、地址传递、引用传递
// 值传递
// 实参把值传递给形参(形参内存里存放的值等于实参的值),交换后形参的值发生改变,但实参的值未发生变化。
void swap1(int x, int y)
{
int temp = x;
x = y;
y = temp;
cout << x << endl;// 2
cout << y << endl;// 1
}
// 地址传递
void swap2(int* p1, int* p2)
{
int temp = *p1;// temp=p1指向的地址存放的值
*p1 = *p2;// p1指向的地址存放的值=p2指向的地址存放的值
*p2 = temp;// p2指向的地址存放的值=temp
}
// 引用传递
void swap3(int &x, int &y)
{
int temp = x;
x = y;
y = temp;
}
int main()
{
int a = 1;
int b = 2;
swap1(a, b);
cout << a << endl; // 1
cout << b << endl; // 2
swap2(&a, &b);
cout << a << endl; // 2
cout << b << endl; // 1
swap3(a, b);
cout << a << endl; // 2
cout << b << endl; // 1
}
指针
// 空指针
int * p = NULL;
// 野指针:指向非法的地址空间(未申请该地址空间就访问)
int * p = (int *)0x1100;// 赋值时要进行类型转换。
内存分区
// 代码区:存放函数体的二进制代码,由操作系统管理。
// 全局区:存放全局变量、静态变量和常量。
// 栈区:存放函数的参数值、局部变量,由编译器自动分配释放。
// 堆区:由程序员分配释放(不释放则由操作系统回收)
malloc和new
// malloc和free
#include <stdlib.h>
// (1)使用malloc函数申请一块空间大小为sizeof(int)的空间,返回指向该空间的指针(类型为void*,申请失败会返回nullptr);(2)使用(int*)强制转换为int*类型的指针;(3)把该指针赋值给int*类型的指针变量p。
int* p = (int*)malloc(sizeof(int));
free(p);
// new和delete
// 利用new创建的数据,会返回该数据对应类型的指针(申请失败会启动c++异常机制处理)。
int* p = new int(10);// 创建一个int型变量,存放10
delete p;
int* p = new int[10];// 创建一个数组
delete[] p;
引用
给变量取别名。
数据类型 &别名 = 原名;
int main()
{
int a = 10;
int c = 30;
// int &b; // error,引用必须要初始化
// 常量引用
// int &b = 10; // error,引用必须引用一块合法的内存空间
// const int &b = 10;
// 相当于int temp = 10; int &b = temp;
int &b = a;// 一旦初始化后,就不可更改引用
// int* const b = &a; // 引用本质是指针常量,所以引用不可更改
b = c;// 赋值操作,而不是更改引用
b = 10;
// *b = 10; //自动解引用
cout << a << endl;// 10
cout << b << endl;// 10
}
int& fn()
{
// int a = 10;
// 错误
// 不要返回局部变量的引用(因为局部变量存放在栈区,当函数执行完毕会被回收,将局部变量返回可能会导致主函数中引用打印出来的值出错)
static int a = 10;
// 静态变量存放在全局区,在程序运行过程中不会被回收
return a;
}
int main()
{
int &ref = fn();
// 如果函数的返回值是引用,函数的调用可以作为左值
fn() = 1000;
cout << ref << endl; // 1000
}
函数
// 默认参数
// 1、如果某个位置有默认参数,则之后的都要有默认参数
int fn(int a, int b = 2, int c = 3)
{
return a + b + c;
}
// 2、函数声明和实现只能有一个有默认参数
int fn(int a = 1, int b = 2, int c = 3);
inf fn(int a, int b, int c)
{
return a + b + c;
}
// 占位参数
// 占位参数也可以有默认参数
void func(int a, int){
...
}
int main()
{
func(10, 10);
}
// 函数重载
// 1、函数参数类型 or 个数 or 顺序不同
// 2、函数的返回值不能作为重载的条件
// 3、引用可以作为重载的条件
void func(int &a)
{
cout << "int &a" << endl;
}
void func(const int &a)
{
cout << "const int &a" << endl;
}
int main()
{
int a = 10;
func(a); // int &a
func(10); // const int &a
}
// 4、当函数重载碰到默认参数,会出现二义性
void func(int a, int b = 10)
{
}
void func(int a)
{
}
int main()
{
func(10);// 出现二义性
}
类和对象
class Person{
public:
// 任何地方都可以访问。
void setName(string name)
{
m_name = name;
}
string getName()
{
return m_name;
}
...
// 构造函数
// 1、函数名称和类名相同
// 2、没有返回值也不写void
// 3、可以有参数,可以发生重载
// 4、创建对象时自动调用
Person()
{
...
}
Person(string name, int height)
{
m_name = name;
m_height = new int(height);
}
// 初始化列表
Person(string name, int height, int age): m_name(name), m_age(age)
{
// 指针的初始化比较特殊
m_height = new int(height);
}
// 拷贝构造函数
// 1、默认拷贝构造函数,是浅拷贝
Person(const Person &p)
{
...
}
// 析构函数
// 1、函数名称和类型相同,名称前加上~
// 2、没有返回值也不写void
// 3、不可以有参数,不会发生重载
// 4、对象销毁前自动调用
~Person()
{
// 用new申请的堆空间记得释放
if(m_height != NULL)
{
delete m_height;
m_height = NULL;
}
...
}
...
protected:
// 类外不能访问,类内和继承类可以访问。
...
private:
// 类外和继承类不能访问,类内可以访问。
string m_name;
int *m_height;
int m_age;
...
}
int main()
{
// 创建实例对象
// 1、括号法
Person p1;// 不要写成Person p1();
Person p2(10);
Person p3(p2);
// 2、显式法
//Person p1;
//Person p2 = Person(10);
//Person p3 = Person(p2);
Person(10);// 匿名对象(当前行执行结束后,系统立即回收掉匿名对象)
cout << "aaa" << endl;
// 执行顺序:构造函数=>析构函数=>打印“aaa”
// 不要用拷贝构造函数初始化匿名对象
// Person(p3);// 编译器会认为成Person p3;(重定义错误:已经定义过了)
// 3、隐式转换法
Person p4 = 10;
Person p5 = p4;// 拷贝构造函数
// 释放时注意可能释放多次,会存在错误
/*
以下代码会因为释放多次产生错误。根据栈的先进后出特性,p2会先释放,然后再释放p1。但是p1释放时,m_height指向的堆空间已经被释放过了,发生错误。
*/
// Person p1(10, 160);
// Person p2(p1);
}
// 对象成员
class A{}
class B
{
A a;
}
int main()
{
// 创建B的实例对象时,运行顺序:A的构造函数=>B的构造函数=>B的析构函数=>A的析构函数
B b;
}
// 静态成员
class Person
{
public:
// 静态成员变量
/*
1、所有实例对象共享同一份数据。
2、在编译阶段分配内存。
3、类内声明,类外初始化。
*/
static int m_a;
// 静态成员函数
/*
1、所有实例对象共享同一个函数。
2、只能访问静态成员变量,不可以访问非静态成员变量。
*/
static void fn(){}
int m_a; // 占的内存空间,属于实例对象
static int m_b; // 不属于实例对象
void func(){}; // 不属于实例对象
static void func(){}; // 不属于实例对象
}
int Person::m_a = 10;// 类外初始化
int main()
{
// 静态成员变量有两种访问方式(静态成员函数也一样)
// 1、通过实例对象
Person p1;
cout << p1.m_a << endl; // 10
Person p2;
p2.m_a = 20;
cout << p2.m_a << endl; // 20
p1.fn();
// 2、通过类
cout << Person::m_a << endl; // 20
Person::fn();
}
// const修饰
class Person
{
public:
int m_age;
mutable int m_height;
// 常函数
// 1、常函数内不可以修改成员属性。
// 2、成员属性声明时加mutable后,在常函数中就可以修改。
void show() const
{
// this本质是指针常量(Person * const this),指针的指向不可修改。
// 变成常函数后,为常量指针常量(const Person * const this)
// m_age = 100; // error, this->m_age = 100;
m_height = 160; // 正确
}
void func(){}
}
int main()
{
// 常对象
// 1、常对象只能调用常函数,其他不行。
const Person p;
p.show();
// p.func(); // error
}
// 继承
class BasicPage
{
...
}
// class 子类 : 继承方式 父类
class Java : public BasicPage
{
...
}
// 继承方式
class A
{
public:
int a;
protected:
int b;
private:
int c;
}
// public共有继承:保持父类的访问权限并继承
class B : public A
{
/*
public:
int a;
protected:
int b;
不可访问:
int c;
*/
}
// protected保护继承:把父亲的除了private以外的属性访问权限改成protected并继承
class B : protected A
{
/*
protected:
int a;
int b;
不可访问:
int c;
*/
}
// private私有继承:把把父亲的除了private以外的属性访问权限改成private并继承
class B : private A
{
/*
private:
int a;
int b;
不可访问:
int c;
*/
}
class Father{
public:
int m_a;
Father()
{
m_a = 100;
}
void show()
{
cout << "father" << endl;
}
void show(int i)
{
cout << i << endl;
}
}
class Son : public Father{
public:
int m_a;
Son()
{
m_a = 200;
}
void show()
{
cout << "son" << endl;
}
}
int main()
{
// 创建Son类实例对象时,运行顺序:Father构造=>Son构造=>Son析构=>Father析构
Son s;
// 同名成员:就近原则
cout << s.m_a << endl; // 200
cout << s.Father::m_a << endl; // 100
s.show(); // son
s.Father::show(); // father
// 如果子类中存在父类同名成员函数,则会隐藏掉所有父类同名成员函数。
// s.show(10); // error
s.Father::show(10);// 10
}
// 多继承
class B: public A, public C
{
...
}
// 菱形继承
class Animal{
public:
int m_Age;
}
// 虚继承解决菱形继承的问题:内部成员通过指针指向同一个内存。
class Sheep: virtual public Animal{}
class Tuo: virtual public Animal{}
class SheepTuo: public Sheep, public Tuo{}
int main()
{
SheepTuo st;
// 以下访问的都是同一份
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;
cout << st.m_Age << endl;
}
// 多态
/*
|- 静态多态:编译阶段确定函数地址
|- 函数重载
|- 运算符重载
|- 动态多态:运行阶段确定函数地址
|- 派生类
|- 虚函数
*/
class Animal
{
public:
// 虚函数(子类重写父类虚函数)实现动态多态
// 纯虚函数
virtual void speak()
{
cout << "Animal is speaking." << endl;
}
}
class Cat: public Animal
{
public:
void speak()
{
cout << "Cat is speaking." << endl;
}
}
class Dog: public Animal
{
public:
void speak()
{
cout << "Dog is speaking." << endl;
}
}
// 父类指针或引用指向子类对象
void doSpeak(Animal &animal)// Animal &animal = cat; //类型转换
{
animal.speak();
}
int main(){
Cat c;
doSpeak(c);// Cat is speaking.
Dog g;
doSpeak(g);// Dog is speaking.
}
// 抽象类
// 1、无法实例化对象
// 2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类,无法实例化对象
class Animal
{
public:
// 纯虚函数
virtual void speak() = 0;
// 虚析构 解决父类指针释放子类对象时不干净的问题
// virtual ~Animal()
// {
// ...
// }
// 纯虚析构(需要声明,也要实现)
// 有了纯虚析构后,也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
}
Animal::~Animal()
{
...
}
int main()
{
Animal *animal = new Cat("Tom");
animal->Speak();
// 通过父类指针去释放,会导致子类对象可能清理不干净(子类中有堆区数据),造成内存泄漏。
delete animal;
}
// 友元:可以访问另一个类中的私有成员
// 1、全局函数做友元
class Building
{
// core
friend void goodG(Building *building);
public:
string m_SittingRoom;
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
private:
string m_BedRoom;
}
void goodG(Building *building)
{
cout << building->m_SittingRoom << endl;
cout << building->m_BedRoom << endl;
}
int main()
{
Building b;
goodG(&b);
}
// 2、类做友元
class Building
{
// core
friend class GoodG;
public:
Building();
string m_SittingRoom;
private:
string m_BedRoom;
}
class GoodG
{
public:
GoodG();
void visit();
private:
Building *building;
}
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
GoodG::GoodG()
{
building = new Building;
}
void GoodG::visit()
{
cout << building->m_SittingRoom << endl;
cout << building->m_BedRoom << endl;
}
// 3、成员函数做友元
class Building
{
// core
friend void GoodG::visit();
public:
Building();
string m_SittingRoom;
private:
string m_BedRoom;
}
class GoodG
{
public:
GoodG();
void visit();
private:
Building *building;
}
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
GoodG::GoodG()
{
building = new Building;
}
void GoodG::visit()
{
cout << building->m_SittingRoom << endl;
cout << building->m_BedRoom << endl;
}
// struct和class的区别
// struct默认权限是公共;class默认权限是私有。
深浅拷贝
浅拷贝
- 拷贝值存放的内存地址。
- 造成堆区内存的重复释放。
深拷贝
- 重新申请空间,存放拷贝值。
// 利用自定义拷贝构造函数解决浅拷贝问题。
Person(const Person &p)
{
m_age = p.m_age;
// m_height = p.m_height; // 默认拷贝构造函数是这么写的
// 深拷贝
m_height = new int(p.m_height);
}
this指针
谁调用,this就指向谁。
class Person
{
public:
int age;
// 解决命名冲突
Person(int age)
{
// 此时this指向新创建的Person实例对象。
this->age = age;
}
// 实现链式调用
// 注意要返回引用,而不是拷贝(如果返回的是拷贝,则无法实现链式调用了!则链式调用修改的是拷贝的值!)
Person& addPerson(Person &p)
{
this->age += p.age;
return *this;
}
void show1()
{
cout << "show" << endl;
}
void show2()
{
// 加该语句防止空指针
if(this == NULL)
{
return;
}
cout << age << endl;// 相当于cout << this->age << endl;
// this等于空指针,this->age不存在。
}
}
int main()
{
Person p1(10);
Person p2(10);
p2.addPerson(p1).addPerson(p1).addPerson(p1);
// 空指针可以调用成员函数
Person * p = NULL;
p->show1();// 正常运行
// p->show2();// error
}
运算符重载
// 加号+
// 1、通过成员函数重载(写在类内部)
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
Person p3 = p1 + p2;// Person p3 = p1.operator+(p2);
// 2、通过全局函数重载(写在文件内部)
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
Person p3 = p1 + p2;// Person p3 = operator+(p1, p2);
// 运算符重载也可以发生函数重载。
Person operator+(Person &p1, int p2)
{
Person temp;
temp.m_A = p1.m_A + p2;
temp.m_B = p1.m_B + p2;
return temp;
}
Person p3 = p1 + 10;
// 内置数据类型的运算符是不能重载的。
// 左移<<
// 只能用全局函数重载,才能化简为cout << p; 如果是成员函数则变成p << cout
ostream& operator<<(ostream &cout, Person &p)
{
cout << "m_A = " << p.m_A << ", m_B = " << p.m_B;
return cout;
}
// 递增++
class MyInteger
{
friend ostream& operator<<(ostream &cout, MyInteger &m);
public:
MyInteger()
{
m_Num = 0;
}
// 咋区分啊?
// 重载前置递增 ++a
MyInteger& operator++()
{
m_Num ++;
return *this;
}
// 重载后置递增 a++
MyInteger operator++(int)
{
MyInteger temp = *this;
m_Num++;
return temp;
}
private:
int m_Num;
}
ostream& operator<<(ostream &cout, MyInteger &m)
{
cout << m.m_Num;
return cout;
}
// 赋值=
class Person
{
public:
int* m_age;
Person(int age)
{
m_age = new int(age);
}
Person& operator=(Person &p)
{
// 如果已存在则释放
if(m_age != NULL)
{
delete m_age;
m_age = NULL;
}
// 深拷贝
m_age = new int(*(p.m_age));
return *this;
}
}
// 关系运算符==、!=
class Person
{
public:
string m_name;
int m_age;
Person(string name, int age): m_name(name), m_age(age){}
bool operator==(Person &p)
{
return this->m_name == p.m_name && this->m_age == p.m_age;
}
bool operator!=(Person &p)
{
return !(this->m_name == p.m_name && this->m_age == p.m_age);
}
}
// 函数调用运算符()
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
}
int main()
{
MyPrint m;
// 由于使用起来特别像函数调用,因此称为仿函数。
m("hello world");
}
文件操作
#include <fsteam>
int main()
{
// 文本文件 - 写文件
// 1、创建流对象
ofstream ofs;
// 2、打开方式
/*
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式谢文件
ios::trunc 如果文件存在,先删除,再创建
ios::binary 二进制方式
*/
ofs.open("test.text", ios::out);
// 通过|符号结合多种打开方式
// ofs.open("test.text", ios::out | ios::binary);
// 3、写入
ofs << "hello" << endl;
ofs << "world" << endl;
// 4、关闭
ofs.close();
// 文本文件 - 读文件
ifstream ifs;
ifs.open("test.text", ios::in);
// 读取方式
// 1、
char buf[1024] = {0};
while(ifs >> buf)
{
cout << buf << endl;
}
// 2、
char buf[1024] = {0};
while(ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}
// 3、
string buf;
while(getline(ifs, buf))
{
cout << buf << endl;
}
ifs.close();
// 二进制文件 - 写文件
ofsteam ofs("test.text", ios::out | ios::binary);
Person p = {"张三", 18};
ofs.write((const char *)&p, sizeof(p));
ofs.close();
// 二进制文件 - 读文件
ifstream ifs;
ifs.open("test.text", ios::in | ios::binary);
if(!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
Person p;
ifs.read((char *)&p, sizeof(Person));
cout << p.m_Name << p.m_Age << endl;
ifs.close();
}
模板【待补充】
// 函数模板
template<typename T>
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
// 使用方法
// 1、自动类型推导
mySwap(a, b);
// 2、显式指定类型
mySwap<int>(a, b);
}
函数对象【待补充】
名称空间
// using声明
int main()
{
using Jill::fetch; // 完成声明后可以使用名称fetch代替Jill::fetch
cin >> fetch;
double fetch; // error, 已经存在同名变量。
}
// using编译指令
using namespace Jack; // 使得名称空间中的所有名称在该作用域内都可用。
// 1、using编译指令放在函数之外
#include <iostream> // 新头文件使用了std名称空间。
using namespace std;
int main()
{
...
cout << "aaaa";
}
// 2、using编译指令放在函数之内。
#include <iostream>
int main()
{
using namespace std;
...
cout << "aaaa";
}
// 3、using声明放在函数之内。
#include <iostream>
int main()
{
using std::cout;
...
cout << "aaaa";
}
// 4、完全不使用
#include <iostream>
int main()
{
...
std::cout << "aaaa";
}
// 5、换头文件吧
#include <iosteam.h>
int main()
{
...
cout << "aaaa";
}
nullptr
// NULL不是c++的关键字,是宏定义
#define NULL 0
// 导致这样初始化的时候,指针p指向内存地址为0的空间
int* p = NULL;
// 常量nullptr:用来初始化指向空值的指针
int* p = nullptr;
// 类型nullptr_t:指针空值类型
nullptr_t p;