C++的指针,引用和STL详解
作者:jiangchao98
对象的定义:对象是指一块能存储数据并具有某种类型的内存空间
一个对象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 &v
某个变量的引用,等价于这个变量,相当于该变量的一个别名
- 定义引用时一定要将其初始化成引用某个变量
- 初始化后,它就一直引用该变量,不会再引用别的变量
- 引用只能引用变量,不能引用常量和表达式
引用作为函数的返回值
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()z
队列中元素个数
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;#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(字符串操作)
其实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") |
重载运算符
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; }
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!