对象的定义:对象是指一块能存储数据并具有某种类型的内存空间
一个对象a,它有值和地址;运行程序时,计算机会为该对象分配存储空间,来存储该对象的值,通过该对象的地址,来访问存储空间中的值。
指针、引用
指针
类型名 * 指针变量名;
每个变量都被存放在从某个内存地址(以字节为单位)开始的若干个字节中;"指针",也称作"指针变量",大小为4个字节(或8个字节)的变量,其内容代表一个内存地址;通过指针,能够对该指针指向的内存区域进行读写。
int * p; //p是一个指针,变量p的类型是int *
T * p; //T可以是任何类型的名字,比如int, double
p 的类型: T*
*p 的类型: T
通过表达式 *p,可以读写从地址p开始的sizeof(T)个字节
*p 等价于存放在地址p处的一个T类型的变量
* 间接引用运算符
sizeof(T*) 4字节(64位计算机上可能8字节)
char ch1 = 'A'
char *pc = &ch1; //使得pc指向变量ch1
&: 取地址运算符
&x: 变量x的地址(即指向x的指针),对于类型为T的变量x,&x表示变量x的地址(即指向x的指针)
&x的类型是T*
指针的作用
使用指针,就有自由访问内存空间的手段
不需要通过变量,就能对内存直接进行操作。通过指针,程序能访问的内存区域就不仅限于变量所占据的数据区域。
指针的相互赋值
不同类型的指针,如果不经过强制类型转换,不能直接互相赋值。
指针的运算
- 两个同类型的指针变量,可以比较大小(比较的是地址空间的大小)
- 两个同类型的指针变量,可以相减(相减值为地址空间差值除以sizeof(T))
- 指针变量加减一个整数的结果是指针
p: T*类型的指针
n: 整数类型的变量或常量
p + n: T*类型的指针,指向地址(地址 p + n * sizeof(T))
n + p, p - n, *(p + n), *(p - n) 分别为地址和地址指向的值
eq:
int a = 10;
int b = 15;
int * p = &a; //0x61fe04
int * q = &b; //0x61fe00
int * ans = p + 2; //0x61fe0c
int k = *(p - 1); //15
- 指针变量可以自增、自减 (p++, ++p, p--, --p)即p指向的地址n + sizeof(T)或者n - sizeof(T)
- 指针可以用下标运算符"[]"进行运算
p是一个T*类型的指针
n是整数类型的变量或常量
p[n]等价于*(p + n)
空指针
地址0不能访问。指向地址0的指针就是空指针
可以用"NULL"关键字对任何类型的指针进行赋值。NULL实际上就是整数0,值为NULL的指针就是空指针
int *pn = NULL;
char *pc = NULL;
int *p2 = 0;
指针作为函数参数(形参是实参的一个拷贝)
指针和数组
数组的名字是一个指针常量(指向数组的起始地址)
T a[N];
a的类型是T*
可以用a给一个T*类型的指针赋值
a是编译时其值就确定了的常量,不能够对a进行赋值
作为函数形参时,T *p和 T p[]等价
void Func(int *p){ cout << sizeof(p); }
void Func(int p[]){ cout << sizeof(p); }
引用
类型名 & 引用名 = 某变量名;(定义了一个引用,将其初始化为引用某个变量)
int n = 4;
int & r = n; //r引用了n, r的类型是int &
某个变量的引用,等价于这个变量,相当于该变量的一个别名
- 定义引用时一定要将其初始化成引用某个变量
- 初始化后,它就一直引用该变量,不会再引用别的变量
- 引用只能引用变量,不能引用常量和表达式
引用作为函数的返回值
int n = 4;
int & SetValue()
{
return n;
}
int main()
{
SetValue() = 40;
cout << n; //输出是40
return 0;
}
常引用
定义引用时,前面加const关键字,即为"常引用"
int n;
const int & r = n; //r的类型是const int &
不能通过常引用去修改其引用的内容:
int n = 100;
const int & r = n;
r = 200; //编译错误
n = 300; //ok
常引用和非常引用的转换
const T &和 T &是不同的类型,T &类型的引用或T类型的变量可以用来初始化const T &类型的引用;const T类型的常变量和const T &类型的引用则不能用来初始化T &类型的引用。
参考:
STL
STL中六大组件
容器(Container),一种数据结构(包含一组元素或元素集合的对象),基本容器:向量(vector), 双端队列(deque), 列表(list), 集合(set), 多重集合(multiset), 映射(map), 多重映射(multimap)。
序列式容器(Sequence containers),其中每个元素均有固定位置--取决于插入时机和地点,和元素值无关(vector, deque, list)
关联式容器(Associative containers),元素位置取决于特定的排序准则以及元素值,和插入次序无关(set, multiset, map, multimap)
迭代器(Iterator)
迭代器Iterator,用来在一个对象集群(collection of objects)的元素上进行遍历。这个对象集群或许是一个容器,或许是容器的一部分。迭代器的主要好处是,为所有容器提供了一组很小的公共接口。迭代器以++进行累进,以*进行提领,因而类似于指针,可以将其视为一种smart pointer。
例如++操作可以遍历至集群内的下一个元素。至于如何完成,取决于容器内部的数据组织形式。
每种容器都提供自己的迭代器,而这些迭代器能够了解容器内部的数据结构
算法(Algorithm)
用来处理群集内的元素。它们可以出于不同的目的而搜寻、排序、修改、使用那些元素。通过迭代器的协助,我们可以只需编写一次算法,就可以将它应用于任意容器,这是因为所有的容器迭代器都提供一致的接口。
仿函数(Functor)
适配器(Adaptor)
提供三种顺序容器适配器:queue(FIFO队列),priority_queue(优先级队列),stack(栈)。
适配器对容器进行包装,使其表现出另外一种行为。倘若要使用适配器,需要加入头文件
分配器(Allocator)
常用容器用法介绍
vector
一个数组必须有固定的长度,在开数组的时候,此长度就被静态地确定下来。但vector却是数组的"加强版",vector理解为一个"变长数组"
事实上,vector的实现方式是基于倍增思想的:假如vector的实际长度为n,m为vector当前的最大长度,那么在加入一个元素的时候,先看一下,假如当前的n=m,则再动态申请一个2m大小的内存。反之,在删除的时候,如果n≥m/2,则再释放一半的内存。
#include<vector>
vector<int>vec;
vector<pair<int, int> >vec_pair;
struct node{ ... };
vector<node>vec_node;
vec.begin(), vec.end() 返回vector的首尾迭代器
vec.front(), vec.back() 返回vector的首尾元素
vec.push_back() 从vector末尾加入一个元素
vec.size() 返回vector当前的长度(大小)
vec.pop_back() 从vector末尾删除一个元素
vec.empty() 返回vector是否为空,1为空,0不为空
vec.clear() 清空vector
vector容器是支持随机访问的,可以像数组一样用[]取值。
vector<int>vec;
vec.push_back(5);
vec.push_back(2);
cout << vec.back() << endl;
for(vector<int>::iterator iter = vec.begin(); iter != vec.end(); iter++)
{
cout << *iter << endl;
}
vector修改值
- 有迭代器,使用迭代器修改 auto iter = v.begin(), *iter = 1
- 使用索引进行修改 v[0] = 1
deque(双端队列)
#include<deque>
deque<int>q
q.begin(), q.end() 返回deque的首尾迭代器
q.front(), q.back() 返回deque的首尾元素
q.push_back() 从队尾入队一个元素
q.push_front() 从队头入队一个元素
q.pop_back() 从队尾出队一个元素
q.pop_front() 从队头出队一个元素
q.size() 队列中元素个数
q.clear() 清空队列
deque支持随机访问,可以像数组下标一样取出其中的一个元素。即q[i]
deque容器可以被应用到SPFA算法的SLF优化:SPFA算法的优化方式
set
set满足互异性,set集合中的元素是默认升序的(set容器自动有序和快速添加、删除的性质是由其内部实现:红黑树(平衡树的一种))
#include<set>
set<int>s
set<pair<int, int> >s;
s.empty() 返回集合是否为空,是为1,否为0
s.size() 返回当前集合的元素个数
s.clear() 清空当前集合
s.begin(), s.end() 返回集合的首尾迭代器(迭代器是一种指针。这里需要注意的是,由于计算机区间“前闭后开”的结构,begin()函数返回的指针指向的的确是集合的第一个元素。但end()返回的指针却指向了集合最后一个元素后面一个元素。)
s.insert(k) 集合中加入元素k
s.erase(k) 集合中删除元素k
s.find(k) 返回集合中指向元素k的迭代器。如果不存在这个元素,就返回s.end(),这个性质可以用来判断集合中有没有这个元素。
s.lower_bound() 返回集合中第一个大于等于关键字的元素
s.upper_bound() 返回集合中第一个严格大于关键字的元素
multiset(有序多重集合)
s.erase(k)
erase(k)函数在set容器中表示删除集合中元素k。但在multiset容器中表示删除所有等于k的元素。
倘若只删除这些元素中的一个元素
if((it = s.find(a)) != s.end())
s.erase(it);
if中的条件语句表示定义了一个指向一个a元素的迭代器,如果这个迭代器不等于s.end(),
就说明这个元素的确存在,就可以直接删除这个迭代器指向的元素。
s.count(k) count(k)函数返回集合中元素k的个数,为multiset所独有。
map
可以根据键值快速地找到这个映射出的数据, map容器的内部实现是一棵红黑树
#include<map>
map<int, char> mp;
建立一个从整型变量到字符型变量的映射
map<int, char>mp;
//插入
mp[1] = 'a';
mp.insert(map<int, char>::value_type(2, 'b'));
mp.insert(pair<int, char>(3, 'c'));
mp.insert(make_pair<int, char>(4, 'd'));
//查找
mp[3] = 't'; //修改键值对中的值
map<int, char>::iterator iter;
iter = mp.find(3);
iter->second = 'y';
cout << iter->second << endl;
//删除
mp.erase(2); //删除键值对
//遍历
for(map<int, char>::iterator iter = mp.begin(); iter != mp.end(); iter++)
{
cout << iter->first << endl;
cout << iter->second << endl;
}
mp.begin(), mp.end() 返回首尾迭代器
mp.clear() 清空函数操作
mp.size() 返回容器大小
queue(FIFO)
#include<queue>
queue<int>q;
queue<pair<int, int> >q;
q.front(), q.back() 返回queue的首尾元素
q.push() 从queue末尾加入一个元素
q.size() 返回queue当前的长度(大小)
q.pop() 从queue队首删除一个元素
q.empty() 返回queue是否为空,1为空,0不为空
priority_queue
优先队列在队列的基础上,将其中的元素加以排序。其内部实现是一个二叉堆。优先队列即为将堆模板化,将所有入队的元素排成具有单调性的一队,方便我们调用。
大根堆声明就是将大的元素放在堆顶的堆。优先队列默认实现的就是大根堆。
小根堆声明就是将小的元素放在堆顶的堆。
#include<queue>
priority_queue<int>q; //大根堆
priority_queue<string>q;
priority_queue<pair<int, int> >q;
priority_queue<int, vector<int>, less<int> >q; //大根堆
priority_queue<int, vector<int>, greater<int> >q; //小根堆
q.top() 返回priority_queue的首元素
q.push() 向priority_queue中加入一个元素
q.size() 返回priority_queue当前的长度(大小)
q.pop() 从priority_queue末尾删除一个元素
q.empty() 返回priority_queue是否为空,1为空,0不为空
stack(栈)
#include<stack>
stack<int> st;
stack<pair<int, int> > st;
st.top() 返回stack的栈顶元素
st.push() 从stack栈顶加入一个元素
st.size() 返回stack当前的长度(大小)
st.pop() 从stack栈顶弹出一个元素
st.empty() 返回stack是否为空,1为空,0不为空
string(字符串操作)
#include<bits/stdc++.h>
using namespace std;
// string的大小和容量
void test2()
{
string s("1234567");
cout << "size=" << s.size() << endl;
cout << "length=" << s.length() << endl;
//下述两个功能不知何意
cout << "max_size=" << s.max_size() << endl;
cout << "capacity=" << s.capacity() << endl;
}
//string的插入: push_back() 和 insert()
void test4()
{
string s1;
// 尾插一个字符
s1.push_back('a');
s1.push_back('b');
s1.push_back('c');
cout << "s1:" << s1 << endl;
// insert(pos, char) 在指定的位置pos前插入字符char
s1.insert(s1.begin(), '1');
cout << "s1:" << s1 << endl;
string::iterator iter = s1.begin() + 2;
cout << *iter << endl;;
s1.insert(iter, '5');
cout << "s1:" << s1 << endl;
}
//string拼接字符串: append() & + 操作符
void test5()
{
//方法一: append()
string s1("abc");
s1.append("def");
cout << "s1:" << s1 << endl; //s1:abcdef
//方法二: + 操作符
string s2 = "abc";
string s3 = "abcde";
//s2 += s3;
s2 += s3.c_str();
cout << "s2:" << s2 << endl;
}
//string的遍历, 借助迭代器或者下标法
void test6()
{
string s1("abcdef"); //调用一次构造函数
//下标法
for(int i = 0; i < s1.size(); i++)
{
cout << s1[i] << ' ';
}
cout << endl;
//正向迭代器
string::iterator iter = s1.begin();
for(; iter < s1.end(); iter++)
{
cout << *iter << ' ';
}
cout << endl;
//反向迭代器
string::reverse_iterator riter = s1.rbegin();
for(; riter < s1.rend(); riter++)
{
cout << *riter << ' ';
}
cout << endl;
}
//string的删除erase()
// iterator erase(iterator p); //删除字符串中p所指的字符
// iterator erase(iterator first, iterator last); //删除字符串中迭代器[first, last)
// string & erase(size_t, pos = 0, size_t len = npos); //删除字符串中索引, 位置pos开始的len个字符
// void clear(); //删除字符串中所有字符
int main()
{
string str1;
//string str; 生成空字符串
string str2("123456");
//string s(str); 生成字符串为str的复制品, str2为"123456"的复制品
string str3("12345", 0, 3);
//string s(str, strbegin, strlen) 将字符串str从下标strbegin开始,长度为strlen的部分作为字符串初始值
string str4("12345", 4);
//string s(cstr, char_len) 以C_string类型cstr的前char_len个字符串作为字符串s的初值
string str5(5, '1');
//string s(num, c) 生成num个c字符的字符串
string str6(str2, 2);
//string s(str, stridx) 将字符串str中从下标stridx开始到字符串结束的位置作为字符串初值 //str4和str6区分不清
//size() 和length() 返回string对象的字符个数,执行效果相同
//max_size() 返回string对象最多包含的字符串,超出会抛出length_error异常
//capacity() 重新分配内存之前,string对象能包含的最大字符数
test2();
test4();
test5();
test6();
return 0;
}
其实string容器就是一个字符串
操作 | string | 字符阵列 |
声明字符串 | string s | char s[100] |
取得第i个字符 | s[i] | s[i] |
字符串长度 | s.length(), s.size() | strlen(s) 不计\0 |
读取一行 | getline(cin, s) | gets(s) |
设成某字符串 | s = "TCGS" | strcpy(s, "TCGS") |
字符串相加 | s = s + "TCGS" | strcat(s, "TCGS") |
字符串比较 | s == "TCGS" | strcmp(s, "TCGS") |
参考:(to_string函数的使用, 将数字常量转化成字符串)to_string函数的用法
参考:(string中find的使用,倘若查找失败,返回string::npos)C++ string中的find()函数
参考:C++中string截取子串、替换子串 C++基础-string截取、替换、查找子串函数
链表操作
参考:
C++中new与malloc的区别
new与delete
- new/delete是关键字,效率高于malloc与free
- 配对使用,避免内存泄漏和多重释放
- 避免交叉使用,比如malloc申请空间delete释放,new空间free释放
- new/delete主要用于类对象的申请和释放。申请的时候会调用构造器完成初始化,释放的时候,会调用析构器完成内存清理
//new
//开辟单地址空间
int *p = new int; //开辟大小为sizeof(int)空间
int *q = new int(5); //开辟大小为sizeof(int)空间,并初始化为5
//开辟数组空间
//一维
int *a = new int[100]; //开辟大小为100的整型数组空间
int *a = new int[100]{0}; //开辟大小为100的整型数组空间,并初始化为0
//二维
int (*a)[6] = new int[5][6];
//delete
//释放单个int空间
int *a = new int;
delete a;
//释放int数组空间
int *b = new int[5];
delete []b;
new与malloc
参数
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自动计算。而malloc则需要显式地指出所需内存的空间。
返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
内存区域
new:分配内存和调用类的构造函数;delete:调用类的析构函数和释放内存。而malloc和free只是分配和释放内存。
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
分配失败
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
内存泄露
内存泄漏对于new和malloc都能检测出来,而new可以指明是哪个文件的哪一行,malloc不可以。
重载运算符
C++语言中已经给出的运算符(算数运算符和逻辑运算符)只是针对C++语言中已经给定的数据类型进行运算。倘若我们想要对我们自定义数据类型进行运算的话,则需要重载运算符,我们可以把重载运算符理解为对已有的运算符的一种重新定义。
重载运算符的实现
语法格式如下
<返回类型> operator <运算符符号>(<参数>)
{
<定义>;
}
//定义结构体
struct node
{
int id;
double x, y;
};
//重载运算符"<"
bool operator < (const node &a, const node &b)
{
if(a.x != b.x)
return a.x < b.x;
else
return a.y < b.y;
}
C++中的pair,tuple,map
#include<bits/stdc++.h>
using namespace std;
pair<string, int> getPreson()
{
return make_pair("Sven", 25);
}
int main()
{
pair<int, int>p1;
pair<int, int>p2(3, 4);
cout << p1.first << ' ' << p2.second << endl;
cout << p2.first << ' ' << p2.second << endl;
pair<string, string>anon;
pair<string, int>word_count;
pair<string, vector<int> >line;
pair<string, string> author("James", "Joy");
pair<string, int> name_age("Tom", 18);
typedef pair<string, string>Author;
Author Joy("James", "Joy");
pair<int, double> p3;
p3.first = 1;
p3.second = 2.5;
cout << p3.first << ' ' << p3.second << endl;
pair<int, double> p4;
p4 = make_pair(1, 1.2);
cout << p4.first << ' ' << p4.second << endl;
pair<string, int>ans = getPreson();
cout << ans.first << ' ' << ans.second << endl;
string name;
int ages;
tie(name, ages) = getPreson();
// tie 起到解包的作用,将返回的pair对解包为name, ages
cout << "name:" << name << ", ages:" << ages << endl;
return 0;
}
tuple即元组,可以理解为pair的扩展,可以用来将不同类型的元素存放在一起,常用于函数的多返回值。
tie解包的使用将pair 或者tuple解包开来
tuple<int, double, string> t3 = {1, 2.4, "3"};
// std::tie: 创建左值引用的tuple,或将tuple解包为独立对象
// 返回左值引用的 tuple 对象
// tie可用于解包std:pair, 因为std::tuple拥有从pair的转换赋值
//std::tie会将变量的引用整合成一个tuple,从而实现批量赋值
int i;
double d;
string s;
tie(i, d, s) = t3;
cout << i << ' ' << d << ' ' << s << endl;
int myint;
char mychar;
tuple<int, float, char>mytuple;
mytuple = make_tuple(10, 2.6, 'a');
tie(myint, ignore, mychar) = mytuple;
//任何值均可赋值给无效果未指定类型的对象。目的是令tie在解包tuple时作为不适用的参数的占位符使用
cout << "myint contains: " << myint << endl;
cout << "mychar contains: " << mychar << endl;
map函数
#include<bits/stdc++.h>
using namespace std;
int main()
{
map<int, string> person;
typedef map<int, string> map_int_cstring;
//map共提供了6种构造函数,涉及到内存分配器相关知识
map<int, string> mapStudent;
//第一种,使用insert函数插入pair
mapStudent.insert(pair<int, string>(0, "student_zero"));
//第二种,使用insert函数插入value_type数据
mapStudent.insert(map<int, string>::value_type(1, "student_one"));
mapStudent.insert(map<int, string>::value_type(1, "student_two"));
cout << mapStudent[0] << endl;
cout << mapStudent[1] << endl; //可见对于mapStudent[1],后面的插入语句并未生效
//第三种,用'array'方式插入
mapStudent[2] = "student_first";
mapStudent[2] = "student_second";
cout << mapStudent[2] << endl; //使用数组插入的方式,可以对value进行覆盖
//在insert函数插入数据,在数据的插入上涉及到集合的唯一性这个概念,即当map中存在该关键字时,insert操作是不能再插入数据的
//用数组插入数据方式,它可以覆盖以前该关键字对应的值
//遍历元素
for(map<int, string>::iterator iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
{
cout << (*iter).first << ' ' << (*iter).second << endl;
}
cout << endl;
//查找元素
//当所查找的关键key出现时,它返回数据所在对象的位置, 如果没有, iter和end函数的值相同
//iter = mapStudent.find("123");
map<int, string>::iterator iter = mapStudent.find(2);
if(iter != mapStudent.end())
{
cout << "Find, the value is " << iter->second << endl;
}
else
{
cout << "Do not find" << endl;
}
//删除与清空元素
// 迭代器删除
iter = mapStudent.find(2);
mapStudent.erase(iter);
//用关键字删除
int n = mapStudent.erase(2); // 如果删除了会返回1, 否则返回0
cout << n << endl;
//用迭代器范围删除,把整个map清空
//mapStudent.erase(mapStudent.begin(), mapStudent.end());
// 等同于mapStudent.clear()
int nSize = mapStudent.size();
cout << nSize << endl;
//map的基本操作函数
//C++ maps是一种关联式容器,包含"关键字/值"对
//begin() 返回map头部的迭代器
//end() 返回map末尾的迭代器
//clear() 删除所有元素
//count() 返回指定元素出现的次数(key值不会重复,只能是0或者1)
//empty() 如果map为空则返回true
//erase() 删除一个元素
//find() 查找一个元素
//insert() 插入一个元素
return 0;
}