【记录】C++

标准库

iostream

// cin读到空格前面
cin << n;

// 读取⼀⾏的字符串,包括空格。
// 使用getline前,应该用getchar()把getline()前面的换行符读取了,否则getline()会读成前面的换行符。
getline(cin, s1);

cstdio

scanf
在这里插入图片描述

#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

next_permutaion

// 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;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值