文章目录
前言
全局对象在程序启动时分配,在程序结束时销毁。局部自动对象:当进入其定义所在的程序块是被创建,在离开块时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。
除自动和static对象外,C++还支持动态分配对象。动态分配对象的生存期与他们在哪里创建无关,只有当显式地被释放时,这些对象才会被销毁。
为了更安全的使用动态对象,标准库定义了两个智能指针类型来管理动态分配的对象。
静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量
栈内存 : 保存定义在函数内的非static对象
二者由编译器自动创建或销毁
static对象:在使用之前分配,在程序结束时销毁
栈 对 象 :仅在其定义的程序块运行时才存在
除了静态内存和栈内存,每个程序还拥有一个内存池。称为自由空间(free store)或堆(help),用于存储动态分配(dynamically allocate)的对象(在程序运行时分配的对象,生存期由程序控制,不使用时必须显式地销毁)
正确的管理动态内存非常棘手
动态内存与智能指针
智能指针也是模板,定义在头文件<memory>
智能指针有点像Python中的对象,有引用计数器,自动释放内存等
关键字:new delete 智能指针: shared_ptr、unique_ptr、weak_ptr
shared_ptr类
shared_ptr<string> p1; //shared_ptr, point to string
shared_ptr<list<int>> p2; //shared_ptr, point to lsit<int>
shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp | 空指针 |
unique_ptr<T> up | 空指针 |
p | p为空 false,p不为空 true |
*p | 解引用 |
p -> mem | |
p.get() | 返回p中保存的指针。 若智能指针释放了其对象,返回的指针所指的对象也就消失了 |
swap(p, q) | 交换p和q中的指针 |
p.swap(q) | 交换p和q中的指针 |
shared_ptr独有的操作
make_shared<T> (args) | 返回一个shared_ptr,指向一个动态分配的类型为T的对象 使用args初始化此对象 |
shared_ptr<T> p(q) | p式shared_ptr q的拷贝;此操作会递增q中的计数器。 q中的指针必须能转换为 T* |
p = q | p q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数, 递增q的引用技术;若p的引用计数变为0,则将其管理的原内存释放 |
p.unique() | 若p.use_count() 为1,返回true;否则返回false |
p.use_count() | 返回与p共享对象的智能指针数量;可能很慢,主要用于调试 |
shared_ptr<int> p3 = make_shared<int>(42); //p3指向 42
shared_ptr<string> p4 = make_shared<string>(4, '9'); //p4指向 string("9999")
shared_ptr<int> p5 = make_shared<int>(); //p5指向 int 初始化0
//p6指向一个动态分配的空vector<string>
auto p6 = make_shared<vector<string>>();
shared_ptr的拷贝和赋值
引用计数(reference count)
auto p = make_shared<int>(42); //p指向的对象只有p一个引用者
auto q(p); //p和q指向相同对象,此对象有两个引用者
auto r = make_shared<int>(42); //r指向的int只有一个引用者
r = q; //给r赋值,令他指向另一个地址
//递增q指向的对象的引用计数器,递减r原来指向的对象的引用计数器
//r原来指向的对象已没有引用者,会自动释放
**shared_ptr 自动销毁所管理的对象······ **
析构函数destructor:与构造函数相反,与对象结束生命周期有关
······shared_ptr 还会自动释放相关联的内存
程序使用动态内存的三种原因:
- 程序不知道自己需要多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据
定义StrBlob类
class StrBlob{
public:
using size_type = std::vector<std::string>::size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
size_type size() const {return this->data->size();}
bool empty() const {return this->data->empty();}
//添加和删除元素
void push_back(const std::string &t){ data->push_back(t);}
void pop_back();
//元素访问
std::string& front();
std::string& back();
private:
std::shared_ptr<std::vector<std::string>> data;
//如果data[i]不合法,抛出一个异常
void check(size_type i, const std::string &msg) const;
};
/*StrBlob构造函数*/
StrBlob::StrBlob(): data(make_shared<vector<string>>() ) {}
StrBlob::StrBlob(std::initializer_list<std::string> il):
data( make_shared<vector<string>>(il) ) {}
/*元素访问成员函数*/
void StrBlob::check(size_type i, const string &msg) const{
if (i >= data->size()) throw out_of_range(msg);
}
string &StrBlob::front(){
//如果vector为空,check会抛出一个异常
check(0, "front on empty StrBlob");
return data->front();
}
string &StrBlob::back(){
check(0, "front on empty StrBlob");
return data->back();
}
void StrBlob::pop_back(){
check(0, "front on empty StrBlob");
data->pop_back();
}
/*StrBlob的拷贝、赋值和销毁*/
直接内存管理
new分配内存,delete释放new分配的内存
int *pi = new int; //pi指向一个动态分配的、未初始化的无名对象
//内置类型或组合类型的对象的值将是未定义的,类类型对象将用默认构造函数进行初始化
string *ps = new string; //初始化为空string
int *pi = new int(1024); //pi指向的对象的值为1024
string *ps = new string(3, '9'); //*ps 为 “999”
vector<int> *pv = new vector<int>{0,1,2,3,4};
string *psl = new string; //默认初始化为空string
string *ps = new string(); //值初始化为空string
int *pi1 = new int; //默认初始化;*pi1的值未定义
int *pi2 = new int(); //值初始化为0;*pi2为0
int a(); //a = 1 不是0??
以下初始化方式不是并列的:
构造初始化:使用圆括号(调用运算符?)
列表初始化:使用花括号
拷贝初始化:使用=,编译器把等号右边的初始值拷贝到新创建的对象中去
直接初始化:不使用等号
值初始化 :只提供对象容纳的元素数量而不用略去初始值。此时库会创建一个值初始化元素初值
默认初始化:使用默认构造函数进行的初始化
auto p1 = new auto {obj}; //✔,p指向与obj类型相同的对象
//该对象用obj进行初始化
auto p2 = new auto{a,b,c}; //❌括号中只能有单个初始化器
const int *pci = new const int(1024);
const string *pcs = new const string;
内存耗尽
若new不能分配所要求的内存空间,抛出类型为bad_alloc的异常
bad_alloc和nothrow都定义在头文件new中
int *p1 = new int; //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int; //如果分配失败,new返回一个空指针
释放动态内存
delete expression
delete p; //p必须指向一个动态分配的对象或是一个空指针,否则是未定义的行为
//销毁对象,释放对应的内存空间
//delete只能释放指针指向的动态内存够对象,释放其他的对象或多次连续释放同一指针是未定义的行为
忘记delete内存会导致人们常说的“内存泄漏”问题
shared_ptr和new结合使用
//就目前而言,智能指针最好与指向动态内存对象的指针绑定
shared_ptr<double> p1; //p1为空智能指针
shared_ptr<int> p2(new int(42)); //p2指向一个值为42的int,该构造方法是explicit的
shared_ptr<int> p2 = new int(42); //❌,必须使用直接初始化的形式
shared_ptr<int> clone(int ival){
return new int(ival); //❌
return shared_ptr<int>(new int(ival)); //✔
}
不要混合使用智能指针和普通指针
void process(shared_ptr<int> ptr){/****/}//ptr离开作用域,被销毁
int* x(new int(1024)); //危险!x是一个普通指针不是一个智能指针
process(x); //❌
process(shared_ptr<int>(x)); //合法,但内存会被释放!
int j = *x; //未定义:x是一个空悬指针!它指向的内存已经被释放
······不要使用get初始化另一个智能指针,或为智能指针赋值
{
shared_ptr<int> p(new int(42)); //引用计数为1
//.get()返回内置指针,指向p管理的对象
int *q = p.get(); //正确,但使用q是要注意,不要让他管理的指针被释放,不能delete
{
//未定义:两个独立的shared_ptr指向相同的内存
shared_ptr<int>(q);
}//程序块结束,q被销毁,它指向的内存被释放
int foo = *p; //未定义:p指向的内存已经被释放了
}//程序块结束 p 所指向的内存会被第二次delete
智能指针和异常
void end_connection(connection *p) {disconnect (*p)};
void f(T &d /* */){
T c = connect(&d);
shared_ptr<connection>p(&c, end_connection);
}//当f退出时(即使时由于异常而推出),connection会被正确关闭
基本规范:
- 不使用相同的内置指针初始化(或reset)多个智能指针
- 不delete get()返回的指针
- 不使用get() 初始化或reset另一个智能指针
- 如果使用get()返回的指针,当最后一个对应的智能指针销毁后,指针就变为无效了
- 如果使用智能指针管理的资源不是new分配的内存,传递给它一个删除器
unique_ptr
unique_ptr<string> p1(new string("abc"));
unique_ptr<string> p2(p1.release()); //将所有权从p1转移给p2,release将p1置为空
unique_ptr<string> p3(new string("Tex"));
p2.reset(p3.release()); //将所有权从p3转移给p2,reset释放了p2原来指向的内存
p2.release(); //❌ p2不会释放内存,而且我们丢失了指针
auto p = p2.release(); //✔ 但我们必须计的delete p
向unique_ptr传递删除器
void f(destination &d /* */){
connection c = connect(&d);
unique_ptr<connection, decltype(end_connection)*> p(&c , end_connection);
//使用连接
}//当f推出(即使是异常退出),connection会被正确关闭
const int& anotherfunc(const int &a){
return a;
}
int main(){
decltype(anotherfunc)* p = anotherfunc;
const int&(*pfunc)(const int&)=0;
pfunc = anotherfunc;
cout << (pfunc == x) << endl;
return 0;}
weak_ptr
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //wp弱共享p;p的引用计数器未改变
if (auto np = wp.lock()){ /* */}
动态数组
两种一次分配一个对象数组的方法
new和数组
int *p = new int[get_size()]; //p为指向数组元素类型的指针,而非指向数组的指针
//未初始化
int *p = new int[10](); //初始化10个0
string *ps1 = new string[10]; //10个空string
string*ps2 =new string[9](9); //9个空string
int *p = new int[10]{1,2,3,4};
string *ps=new string[10]{"a", string(3,'x')};
若,初始化器的数目大于元素数目,则new表达式失败,抛出pad_array_new_length异常,定义在new头文件中
char arr[0]; //❌不能定义长度为0的数组
char *cp = new char[0]; //✔但cp不能解引用
释放动态数组
delete p; //p必须指向一个动态分配的对象或为空,否则是未定义行为
delete [] p; //p必须指向一个动态分配的数组或为空,否则是未定义行为
智能指针和动态数组
//up指向一个包含10个未初始化int的数组
unique_ptr<int[]> up(new int[10]);
up.release(); //自动调用delete[]销毁其指针
for(size_t i=0; i!= 10; ++i)
up[i] = i;
shared_ptr不直接支持管理动态数组
不要用动态数组,用vector等容器
//为了使用shared_ptr, 必须提供一个删除器
shared_ptr<int> sp(new int[10], [](int *p){delete[]p;})
sp.reset(); //使用我们提供的lambda释放数组,它使用delete[]
//否则,shared_ptr默认使用delete,就会碰到delete 动态数组指针的未定义行为
//shared_ptr未定义下标运算符,并且不支持指针的算术运算
for (size_t i=0; i!=10; ++i)
*(sp.get() + i) = i; //使用get获取一个内置指针
allocator类
allocator类定义在头文件memory中
allocator<string> alloc;
auto const p = alloc.allocatr(n);
auto x = p;
alloc.construct(x++); //*x为空字符串
alloc.construct(x++, 10, 'c'); //*x为 cccccccccc
alloc.construct(x++, "hi"); //*x为 hi
cout << *p << endl; //正确
cout << *x << endl; //严重错误,x指向为构造的内存
while(x != p)
alloc.destroy(--x) ; //释放我们真正构造的string,析构
alloc.deallocate(p, n); //释放内存
拷贝和填充未初始化内存的算法
//vector<int> vi
auto p = alloc.allocate(vi.size()*2);
auto x = uninitialized_copy(vi.begin(), vi.end() ,p);
uninitialized_fill_n(x, vi.size(), 42)
//alloc总共n = vi.size()*2个元素,前n个拷贝,后n个赋值42
使用标准库:文本查询程序
特别辣的鸡
class TextQuery{
using inner_t1 = map<string, pair<int, set<int>>>;
private:
inner_t1 wd2lineMap;
vector<string> text;
ifstream& infile;
void Map_function();
public:
TextQuery(ifstream &infile):
infile(infile), wd2lineMap(), text() {Map_function();};
string query(const string&);
};
void TextQuery::Map_function(){
if(infile){
string line;
int lineNum=-1;
while ( getline(infile, line) ){
++lineNum;
text.push_back(line);
istringstream wds(line);
string wd;
while(wds >> wd){
auto res = wd2lineMap.find(wd);
if (res == wd2lineMap.end()){
auto paired = pair<int, set<int>>(1, {lineNum});
wd2lineMap.insert({wd, paired});
}else
++(wd2lineMap[wd].first);
wd2lineMap[wd].second.insert(lineNum);
}
}
}
}
string TextQuery::query(const string&s){
auto iter = wd2lineMap.find(s);
if (iter == wd2lineMap.end()){
return string("not found!\n");
}else{
string res;
auto paired = wd2lineMap[s];
cout << "word: "<< s << " occures "<<paired.first<< (paired.first>1?" times":" time") << endl;
for (auto c:paired.second)
if (c>=0 && c<text.size())
res = res + "(line " + to_string(c)
+ ") " + text[c] + "\n";
else
throw out_of_range("index out of vector");
return res;
}
}
void runQueries(ifstream &infile){
TextQuery tq(infile);
while(true){
cout << "enter word to look for, or q to quit:\n";
string s;
if (!(cin >> s) || s == "q") break;
// cout << tq.query(s);
print(cout , tq.query(s)) << endl;
}
}
int main(){
ifstream infile("./text.txt");
runQueries(infile);
return 0;}
比较优秀的代码
#include <iostream>
#include <fstream>
#include <sstream>
#include <set>
#include <map>
#include <vector>
#include <memory>
using namespace std;
class QueryResult{
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
using line_no = std::vector<std::string>::size_type;
QueryResult(std::string s,
std::shared_ptr<std::set<line_no>> p,
std::shared_ptr<std::vector<std::string>> f):
sought(s), lines(p), file(f) {}
private:
std::string sought;
std::shared_ptr<std::set<line_no>> lines;
std::shared_ptr<std::vector<std::string>> file;
};
class TextQuery{
public:
using line_no = std::vector<std::string>::size_type;
TextQuery(std::ifstream&);
QueryResult query(const std::string&) const;
private:
std::shared_ptr<std::vector<std::string>> file;
std::map<std::string,
std::shared_ptr<std::set<line_no>>> wm;
};
TextQuery::TextQuery(ifstream &is):file(new vector<string>){
string text;
while (getline(is, text)){
file->push_back(text);
int n = file->size() - 1;
istringstream line(text);
string word;
while (line >> word){
auto &lines = wm[word]; //必须用引用
if (!lines)
lines.reset(new set<line_no>); //
lines->insert(n);
}
}
}
QueryResult TextQuery::query(const std::string&sought) const{
static shared_ptr<set<line_no>> nodata(new set<line_no>);
auto loc = wm.find(sought);
if (loc == wm.end())
return QueryResult(sought, nodata, file);
else
return QueryResult(sought, loc->second, file);
}
ostream &print(ostream& os, const QueryResult &qr){
os << qr.sought << " occurs " << qr.lines->size() << " "
<< (qr.lines->size()>1?" times": " time") << endl;
for (auto num:*qr.lines)
os << "\t(line " << num+1 << ") "
<< *(qr.file->begin() + num) << endl;
return os;
}
void runQueries(ifstream& infile){
TextQuery tq(infile);
while(true){
cout << "enter word to look for, or q to quit: ";
string s;
if (!(cin>>s) || s == "q") break;
print(cout, tq.query(s)) << endl;
}
}
int main(){
ifstream infile("./text.txt");
runQueries(infile);
return 0;}
总结
C++中,动态内存通过new表达式分配,通过delete表达式释放。标准库还定义了allocator类来分配动态内存块。
尽量不要用手动的方式管理动态,用智能指针