C++Primer学习记录——【截至第十二章 动态内存】

5 Views

重温C++,从经典作开始看起,记录自己遇到的新知识和可能犯的错误。


1.1字节为8bit,机器的字由4或8字节构成
2.基本的字符类型是char,一个char占用1字节的空间
3.float占用1个字,有效位数为7位;double占2个字,有效位数为16位
4.16进制标识:0x或0X,8进制标识:0,浮点数标识:E或e
5.定义全局变量A后,又在局部定义A,此时使用A调用的是局部A,想调用全局A需使用::A
6.extern用于在多个文件之间共享变量名,在一个文件内定义,多个文件内声明。不能在函数内部声明
7.引用必须被初始化,指针可以不初始化。引用一旦建立不能更改绑定,指针可以更改但是更改时不需要加*。引用和指针的类型都必须和原变量相同
8.const放在*前面定义为指向常量的指针,const放在*后面定义为常量指针。如:

int i=42;
const int *p1=&i;//指向常量的指针
int *const p2=&i;//常量指针
const int *const p3=&i;//指向常量的常指针

指向常量的指针可以不初始化,且可以更改指向的地址,指向的值不能用此指针更改,但是可以用其他方式修改;
常量指针必须初始化,不能更改指向的地址,指向的值可以用此指针修改,也可以用其他方式修改;
指向常量的常指针必须初始化,不能更改指向地址,不能用此指针更改值,可以用其他方式修改指向值;

int i=1,j=2;
const int *p1;//可以不初始化
p1=&i;p1=&j;//可以更改绑定
//*p1=3;//不可以直接修改
j=4;//可以用其他方式修改原值
int *const p2=&i;//必须初始化
//p2=&j;//不可以更改绑定
*p2=6;//可以直接修改

9.顶层const:指针本身是个常量(常量指针);底层const:指针指向的值是常量(指向常量的指针)。执行拷贝操作时,若A是底层const,则拷贝的B也必须是底层const。
10.constexpr可检测定义的初始值是否是常量表达式,若不是则会编译报错。这样一来会导致初始化时无法使用函数,此时将函数声明为constexpr则不会出现编译错误(此函数必须足够简单)
11.constexpr指针只能指向nullptr或0或固定地址的对象,constexpr定义的指针必定是常量指针
12.别名的声明方法有typedef和using,如

typedef int myint;using mydouble=double;

创建别名时使用指针。不能把原数据类型带入别名使用,会使得定义的类型理解出现问题;

typedef char *pstring;
const pstring cstr=0;//常量指针
const char *cstr2=0;//指向常量的指针

13.auto定义的变量由系统自动判断类型,但是在同时定义多个变量时,变量的数据类型必须一致,auto会忽略顶层const
14.decltype(expression)用给定的expression类型定义变量。若decltype使用的表达式是变量则decltype返回该变量的类型;如果表达式是解引用操作,则得到的类型为引用;表达式如果是加了括号的变量,其结果无论表达式如何,一定是引用;包含赋值的表达式,会产生包含左值的引用类型

int i=42,*p=&i,&r=i;
int j=0;
decltype((i)) c=i;//c的类型为int&
decltype(i) d=i;//d的类型为int
decltype(*p) e=&i;//e的类型为int&
decltype(i=j) f=i;//f的类型为int&

15.使用cin>>str获取string类型的输入,会自动忽略开头和结尾的空格和制表符等。使用getline(cin,str)则会保留一行完整的输入直到获取到回车符
16.string和字符串字面值不是一个类型,执行加法时要求两边至少有一边为string类型。即

“a”+str+”b”;//正确
“a”+”b”+str;//错误

17.vector初始化时,花括号和圆括号有很大不同,圆括号对应构造函数,花括号对应初始化列表

vector<int> v1(10);//v1有10个元素,每个值都是0
vector<int> v2{10};//v2有1个元素,值是10
vector<int> v3(10,1);//v1有10个元素,每个值都是1
vector<int> v4{10,1};//v1有2个元素,值是10和1

但是当构建的对象类型不能用来列表初始化时,情况又有不同

vector<string> v5(“hi”);//不可以这么构建v5!
vector<string> v6{“hi”};//v6有1个元素,值是”hi”
vector<string> v7(10);//v7有10个元素,每个值都是string的默认值
vector<string> v8{10,“hi”};//v8有10个元素,每个值都是”hi”

18.迭代器遍历容器,迭代器的类型为iterator和const_iterator或者直接使用auto定义变量。const_iterator只支持读操作,iterator可读可写。若容器对象也是常量
,则只能使用const_iterator
19.在vector容器里,想使用const_iterator但又用auto自动获取类型时,可用cbegin()和
cend()方法取代begin()和end()
20.定义指针和引用绑定数组时,必须声明数组的维度

int arr[10];
int (*p)[10]=&arr;
int (&q)[10]=arr;

21.在引入头文件iterator后,使用begin(),end()可以分别获得一个可迭代的对象的首址和尾址,两者相减可得元素个数
22.C风格字符串末尾均以\0结束,比较时使用内置函数strcmp,若字符串末尾不含\0将出现严重错误,C风格字符串定义为char[],C++建议使用string。为了向下兼容,string提供c_str()方法将字符串转换为C风格字符串
23.使用数组初始化vector

int arr[]={1,2,3,4,5};
vector<int> ivec(begin(arr),end(arr));//全部拷贝
vector<int> ivec2(arr+1,arr+3);//拷贝第2~4个元素

24.范围for遍历二维数组时,第一层遍历要添加引用。否则使用auto获取一维索引时,系统判断得到的类型为指针类型,将不能在第二个for中遍历

for(const auto &row:arr)
    for(auto col:row)
        cout<<col<<endl;

25.左值与右值:赋值运算产生左值;取地址产生右值;解引用运算、下标运算产生左值;内置类型和迭代器的递增递减运算产生左值。使用decltype作用于左值表达式得到引用
26.取余操作会根据符号产生影响,如m%(-n)=m%n;(-m)%n=-(m%n);
27.递增递减语句分前置和后置版本(++a,a++,--a,a--)。在C++中,后置版本需要将原始值存储下来以便返回这个未修改的内容,如果不需要修改前的值,这种操作是对空间的浪费,所以如果非必须,推荐使用前置版本。
28.使用sizeof对对象求占用空间:
(1)对指针执行sizeof运算得到指针本身所占空间的大小
(2)对解引用指针执行sizeof,无论指针是否为空,结果始终为指针本身占用空间大小(一般32位系统为4字节,64位8字节)
(3)对数组执行sizeof,结果为整个数组占用空间大小
(4)对结构体或类执行sizeof,结果为结构体、类的占用空间最大类型的整数倍,即结构体含有三个成员short a;int b;char c;占用空间为12字节
(5)特别注意:把数组作为参数传入函数,数组会被退化为指针,此时对数组求sizeof,结果与(1)相同

#include<iostream>
using namespace std;
int testSizeofArr(int arr[]);
int main() {
    int x[3] = {1,2,3};
    int *p = x;
    cout<<sizeof(x)<<" "<<sizeof(*x)<<endl;
    cout<<sizeof(p)<<" "<<sizeof(*p)<<endl;
    testsizeofarr(x)
    return 0
}
int testsizeofarr(int arr[]) {
    cout<<sizeof(arr)<<endl
}

29.
显式类型转换cast-name<type>(expression),type为转换的目标类型,expression是转换的值,cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。static_const可进行任意不含有底层const的转换,const_cast只能改变运算对象的底层const,reinterpret_cast为运算对象的位模式提供较低层次上的重新解释,reinterpret_cast的使用非常危险!。

double a=1.3;
int a2=static_cast<int>(a);//编译器将不会给出精度丢失的警告
const char b=’1’;
char *p=const_cast<char*>(&b);
//*p='2';//虽然p不是常量指针,但是依然不能使用p修改b的值
int c=55;
int *ip=&c;
char *pc=reinterpret_cast<char*>(ip);
//虽然pc是一个char*,但它却指向int,不能当做char*使用

30.switch语句要注意在每一个case后添加break,否则在某一个case判断成功后,其后的case都会不经过判断自动执行代码;
31.switch语句在一个case中声明变量(只声明,不初始化),即使这个case被跳过,在下一个case依然可以对这个变量进行操作,这属于编译器特性(在程序运行前预先为变量申请空间)

bool ch=false;
switch(ch){
    case true:
        //string file_name;//错误定义
        //int ival = 0;//错误定义
        int jval;//正确定义
        break;
    case false:
        jval=1;//可对jval赋值
        //if(file_name.empty())//不可对file_name操作,因为他没有被正确声明
        //continue;
}

32.C++中,try catch不能捕获除0异常,发生除0行为将直接造成程序错误而被终止
33.函数传入的形参和函数内部定义的变量均为局部变量,函数执行结束后会被自动销毁,想要延长对象的使用时间,可将局部变量定义成static类型转换为局部静态对象,这样直到程序终止时才会被销毁。
34.使用引用避免拷贝:拷贝大的类类型对象或容器时比较低效(如特别长的string类型),若对传参不做修改,将传参替换为常量引用能提高效率。另外,尽量使用常量引用会避免诸多意外的错误(如常量字符串作为参数,但形参是普通引用会引发编译错误)。
35.main函数形参(int argc,char **argv),注意argv在接受输入参数时,从下标1开始存储,0存储的是程序的名字
36.initializer_list处理函数实参数量未知但所有实参类型都相同的情况,传递的多个参数必须放在花括号{}内

int error_msg(initializer_list<string> il){
    for(auto beg=il.begin();beg!=il.end();++beg) {
        cout<<*beg<<" ";
    }
    cout<<endl;
    return 0
}
int main(int argc,char **argv){
    error_msg({"a","b","c"});

37.函数返回值为引用类型时,返回的实际结果是某一值的引用,不会产生拷贝。要求该值一定不能是局部对象的引用或指针
38.使用多种形式返回数组指针:

#include<iostream>
using namespace std;
using string2=string[3];
string odd[]={"1","3","5"};
string(* test())[3]{//普通返回类型
    return &odd;
}
string2 *test2(){//别名返回类型
    return &odd;
}
auto test3() -> string(*)[3]{//尾置返回类型
    return &odd;
}
decltype(odd) *test4(){//decltype返回类型
    return &odd;
}
int main(int argc,char **argv){
    string (*s)[3]=test();
    for(auto *i=begin(*s);i!=end(*s);++i)
        cout<<*i<<" ";
    return 0;
} 

39.函数的形参拥有顶层const时与不含顶层const相同(不可重载),但形参为const引用或const指针时和原函数不同(可重载),此时函数返回值可通过const_cast和重载函数转换为非常引用(但我不是很明白这样做有什么意义...)

const string & shortString(const string &s1,const string &s2) {
    cout<<"1"<<endl;
    return s1.size() < s2.size() ? s1 : s2;
}
string & shortstring(string &s1,string &s2){
    cout<<"2"<<endl;
    auto &r = shortString(const_cast<const string&>(s1), const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

40.只有单一情况下,如函数int test(int a,double b),调用test(1,1)后一项1会被强制类型转换为double。但是如果同时存在重载函数double test(double a,int b),此时会存在二义性,test(1,1)无法匹配计算,出现编译错误。
41.C++设置默认实参时,只能省略尾部的实参
42.内联函数的声明方式为在普通函数前添加inline,内联函数能够减少函数运行的开销。该机制适用于优化规模较小、流程直接、频繁调用的函数,有很多编译器都不支持内联函数
43.assert(expr),expr为真则继续,否则终止程序并输出错误信息,assert用于调试代码,保证一些位置不会出现不可能出现的值。但是assert非常占用资源,在调试程序无误后,使用#define NDEBUG取消所有assert的执行
44.几个常用的编译器定义的局部静态变量:
(1)__func__当前调试的函数名
(2)__FILE__存放文件名的字符串字面值
(3)__LINE__存放当前行号的整形字面值
(4)__TIME__存放文件编译时间的字符串字面值
(5)__DATE__存放文件编译日期的字符串字面值
45.指针函数,定义一个函数后,使用指针替换函数名再声明一个函数,该函数为指针函数,效果与原函数相同,调用时无需提前解引用指针

bool lengthCompare(const string &A,const string &B){
    return A.size() < b.size();
}
bool (*pf)(const string &,const &);
int main(int argc,char **argv) {
    pf = lengthCompare;
    b1 = pf("A","BB");
    b2 = (*pf)("A","BB");
    b3 = lengthCompare("A","BB");
    return 0;
}

这种声明方法看上去很麻烦,使用typedef和decltype简化定义:

typedef decltype(lengthCompare) *pf;

46.类中隐藏的this指针通常是指向类类型的非常量版本的常量指针,这导致我们不能把this绑定到常量对象上,也不能在一个常量对象上调用普通的成员函数。通过在函数名后添加const,可修改隐式this的类型

string isbn() const(return this->bookNo;}

47.如果类内不含任何构造函数,则编译器会自动生成默认的构造函数。如果我们希望自定义的构造函数和默认构造函数功能完全相同,定义时在参数列表后使用 =default,如果该函数在类内,默认构造函数是内联的,类外则不是内联的。
48.类外函数访问类内私有成员的方法为:将该函数在类内定义为友元函数,如

friend int print(Person &p);

友元函数不受private等控制访问权限的影响
友元关系不存在传递性,即类A是类B的友元类,类C是类A的友元类,则类C不是类B的友元类
类外的友元函数在类内被调用时,不仅需要在类内声明一次,还需要在类外声明一次(与普通函数声明的方式类似),否则部分编译器会找不到该函数的定义
49.可变数据成员:mutable修饰词加在成员的定义前,即使是const修饰的函数,也可以对该成员修改
50.使用*this指针返回对象本身的引用,函数定义为引用,返回值为调用该函数的对象

#include<iostream>
#include<string>
using namespace std;
class Screen {//屏幕窗口模拟类
public:
    using pos = string::size_type;
    //因为包含了另一个构造函数,所以必须手动默认无参数的构造函数
    Screen()=default;
    Screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}
    //获取光标位置的字符
    char get() const{return contents[cursor];}
    //获取指定位置的字符
    inline char get(pos ht,pos wd) const;
    //移动到指定位置
    Screen &move(pos r,pos c);
    //更改光标位置字符
    Screen &set(char);
    //更改指定位置字符
    Screen &set(pos,pos,char);
    const Screen &display() const;
private:
    pos cursor=0;
    pos height=0,width=0;
    string contents;

};

inline Screen &Screen::move(pos r,pos c) {
    pos row = r * width;
    cursor = row + c;
    return *this;
}
char Screen::get(pos r,pos c) const {
    pos row = r * width;
    return contents[row + c];
}
inline Screen &Screen::set(char c) {
    contents[cursor] = c;
    return *this;
}
inline Screen &Screen::set(pos r,pos col,char ch) {
    contents[r*width + col] = ch;
    return *this;
}
inline const Screen &Screen::display() const {//返回常引用
    cout<<contents<<endl;
    return *this;
}
int main() {
    screen myscreen;
    myscreen.move(4,0).set('#');
    cout<<myscreen.get(4,0)<<endl;
    myscreen.display();
    myscreen.display().set('#');
}

错误调用 **这里做了个实验,不给string赋初值的情况下直接用索引修改值 *dev编译器,x64系统,在修改索引23576时出现程序异常,低于这个索引不会报错 *但是不管怎么修改值,输出的s值始终为空

string s;
s[0]="1" ;
s[23575]="2";
cout<<s<<endl;

51.定义类外的成员函数时,如函数 A::B A::F(C c){};,A为类名,B为类A中的数据类型,F为类外函数,C也为A中的数据类型。可以发现,对数据类型B和函数名F都需要使用::表明其属于类A,但是F的参数类型C不需要。F声明后,其参数和函数内部成员在函数作用域内,故不需要添加::,而对F的声明在函数作用域外,所以需要添加::。
52.类中成员为const或引用时必须被初始化,必须通过构造函数初始化列表为这些成员提供初值。
53.使用初始化列表为成员赋初值,如:X(val):j(val),i(j){},如果在定义成员时先定义i后定义j,这里会出现未定义错误,因为编译器默认按照成员的声明顺序为他们初始化,所以应尽量避免使用成员相互初始化的情况出现。
54.构造函数可以选择其他构造函数作为自己的委托构造函数,减少代码量

class Sales_data{
public:
    Sales_data(string s,unsigned cnt,double price):
        bookNo(s),units_sold(cnt),revenue(cnt*price){}
    Sales_data():Sales_data("",0,0){}
    Sales_data(string s):Sales_data(s,0,0){}
    //Sales(istream &id):Sales_data(){read(is,*this);}
private:
    unsigned units_sold;
    string bookNo;
    double revenue;
}; 

55.使用explicit禁止隐式调用单参数类的构造函数,这么做的好处是增加代码的可阅读性

class Sales_data{
public:
    explicit Sales_data(double price):revenue(price){}
private:
    double revenue;
};

56.聚合类的定义:
(1)所有成员均为public
(2)没有定义构造函数
(3)没有类内初始值
(4)没有基类和虚函数
聚合类可使用花括号初始化成员,但顺序一定要和类内定义顺序相同:

struct Data{
    int a;
    string s;
};
Data d={0,”hello”};

57.为类的成员或函数添加关键词static使得该成员或函数可被全局访问,但如果该函数在类外定义,则只需要在类内声明时添加static,类外不添加static
58.IO类型的对象不能进行拷贝和赋值,传递和返回的引用不能是const
59.C++中STL的顺序容器,大部分支持赋值操作,但array的赋值要求两边必须具有相同的类型

vector<int> v={1,2,3};//正确
v={1,2);//正确
array<int,3> a={0}//正确;
//a={0}//错误;
// 赋值操作还可使用assign
list<string> names;
vector<const char*> oldstyle;
//names=oldstyle;//错误,容器类型不匹配
names.assign(oldstyle.cbegin(),oldstyle.cend()); 

60.同类型容器的交换操作使用swap。除string外,swap后指针均不会失效;除array外,swap不会对两个容器的元素值真正交换,所以swap的时间是常数级别的。
61.顺序容器在遍历的同时进行插入和删除应注意指针的改变(注意,不要保存并使用end()的位置,这可能会导致代码产生死循环)

vector<int> v={0,1,2,3,4,5,6,7,8,9};
auto iter=v.begin();
while(iter!=v.end()){
    if(*iter%2){//奇数
        v.insert(iter,*iter);//原位置插入
        iter+=2;//跳过当前插入位置以及后移的元素位置
    }else{//偶数
        v.erase(iter);//删除后不需要改变指针位置
    }
} 

62.vector和string在新插入元素导致预分配的内存空间不足时,会申请更大的一片内存区域(比插入元素后总占用空间还要大),把所有元素拷贝到新的内存区域。C++还提供了一些管理容器vector和sring的成员函数
shrink_to_fit() 将容器的容量调整到和容器size相同的大小
capacity() 得到容器目前可保存的最大元素个数
reserve(n) 分配空间为至少容纳n个元素的大小
63.只读算法accumulate(begin,end,type),该函数根据type,从begin到end执行累加操作,如果type类型不包含’+’操作则会出现编译错误。

vector<string> v={"1","2","3"};
cout<<accumulate(v.begin(),v.end(),""); //编译错误
cout<<accumulate(v.begin(),v.end(),string("")); //编译正确

只读算法equal(begin1,end1,begin2),从容器1的begin到end,和容器2的begin2到begin2+end1-begin1,元素比较是否相等,通过’==’来比较,若容器2长度不足会出现意外错误
64.写算法fill(begin,end,value),将begin到end的所有值重置为value,fill不检查合法性,如果有长度为3的容器使用fill修改10个元素值,则会通过编译但在运行时出现严重错误。
写算法back_inserter(container),向容器写入元素,函数返回值为对应容器的尾部指针

vector<int> v;
back_inserter(v)=42;

写算法copy,与euqal类似,功能为将容器1的对应元素拷贝到容器2,同样对容器2有长度要求。
写算法replace(begin,end,val1,val2),将begin到end的所有值为val1的元素替换为值为val2
的元素,如果需要保留原序列,将替换后的新序列存入其他容器,可使用

replace_copy(begin,end,back_inserter(container),val1,val2)

65.重排算法sort(begin,end)从begin按照’<’排序
unique将非重复元素排列到容器首部,重复值排到尾部,返回值为重复值开始位置的指针

vector<int> v={1,3,5,7,9,2,3,5,4,6,8};
sort(v.begin(),v.end());
auto end_unique=unique(v.begin(),v.end());
v.erase(end_unique,v.end());
for(auto i : v){
    cout<<i<<endl; 
} // 输出1,2,3,4,5,6,7,8,9

66.使用定制操作修改各算法的运算规则
根据string长度对vector排序:

bool isShorter(const string &a,const string &b){
    return a.size()<b.size(); 
}
int main(int argc,char **argv) {
    vector<string>
    v={"aaa","bb","c"};
    sort(v.begin(),v.end());
    for(auto i : v){
        cout<<i>>endl; // 输出aaa="" bb="" c=""
    cout<<"my="" rule:">>endl;
    sort(v.begin(),v.end(),isshorter) //输出c="" aaa="" 
    return 0; 

在上面例子中,要使得相同长度的单词仍按照字典序排序,可将sort替换成stable_sort
67.C++的lambda表达式与python类似,但是语句略长,如将上面的isShorter函数定义成lambda表达式:

vector<string> v={"aaa","bb","c"};
auto isShorter=[](const string &a,const string &b){return a.size()<b.size();}; sort(v.begin(),v.end(),isshorter);
// lambda还可以使用局部变量,前提是在[]将该变量捕获
auto f = [sz](const string &a){return a.size()>=sz;};

捕获的变量也可以是引用,但是必须注意引用的作用范围。某些对象没有拷贝功能(如ostream),捕获的唯一方法就是使用引用
多个值捕获时,先声明默认捕获形式’=’(值捕获)’&’(引用捕获),按照这种方式捕获所有变量,如[=,&os]和[&,c]
想要改变捕获变量的值,使用关键字mutable

[v]() mutable {return ++v;};

lambda表达式含有多个语句时,返回值为void,想自定义返回类型,必须使用尾置返回类型

[](int i){return i<0?i:-i;};//合法
[](int i){if(i<0) return i;else return -i;};//不合法,返回值int和void不匹配
[](int i) -> int {if(i<0) return i; else return -i;};//合法,使用尾置返回类型

使用for_each配合lambda简化代码:

vector<string> v={"aaa","bb","c"};
for_each(v.begin(),v.end(),[](const string & s){
    cout<<s<<endl;
});

68.函数适配器bind,该函数接受一个对象,并生成新的对象来适应原对象的参数列表。bind定义在标准库functional中

auto g = bind(f,a,b,_2,c,_1);
//g(X,Y)==>f(a,b,Y,c,X);

69.函数ref(object),返回对象的引用。函数cref(object)返回对象的const引用
70.pair(object1,object2)表示定义一种对应关系,它被定义在头文件utility中,map中的每个元素都是pair,pair的元素都是public的,可通过first、second访问
71.set容器的迭代器是只读的,访问过程中不可修改
72.向map中插入元素,使用insert函数,有几种比较简便的方式

map[first]=second;//如果key值first已存在则替换second,否则插入
map.insert({first,second});
map.insert(make_pair(first,second));
// insert函数返回一个pair,这个pair的first为指向插入元素的指针,second为bool,插入成功为true否则false
map<string,int> m;
for(int i=0;i<10;++i){
    string first=to_string(i);
    int second=i;
    m.insert({first,second});//第一种插入方式
    auto k = m.insert(make_pair(first,second));//第二种,此时会插入失败
    if(!k.second) 
        ++k.first->second;//失败时对second累加
}//此时m中元素为:{{0,1},{1,2},{2,3},{3,4}....}

向map中删除元素,使用erase(key_value),erase返回值为int,表示删除的元素个数
73.在可重复的multimap中按key查找元素,lower_bound(key)返回第一个查找到的位置,upper_bound(key)返回最后一个查找到的位置的尾后位置

for(auto beg=m.lower_bound(key),end=m.upper_bound(key);beg!=end;++beg){
    cout<<beg->second<<endl; 
}

或者使用equal_range(key),该函数返回一个pair,first为lower_upper(key),second为upper_bound(key)
74.map和set都是默认按照key字典序排序的,unordered_map和unordered_set为无序存放元素。map和set存储结构为平衡二叉树和红黑树,读取速度较快,但是插入较麻烦;而无序容器使用hash桶存放,对key做hash后将对应pair存放在一个桶中,查找元素时先寻找桶,再去桶中寻找元素

c.bucket_count();//当前桶的个数
c.max_bucket_count();//容器可容纳的最大桶数量
c.bucket_size(n);//第n个桶中当前有多少元素
c.bucket(n);//元素n在当前的哪个桶中

75.对象的存储方式:
(1)静态内存存放局部static对象,在使用前分配,程序结束时销毁
(2)栈内存存放函数内非static对象,在对应程序块运行时才存在
(3)自由空间(堆)存储动态分配的对象,当他们不再被使用时,我们必须显式销毁
76.智能指针类型shared_ptr、unique_ptr、weak_ptr。shared_ptr运行多个指针指向同一个对象;unique_ptr独占指向的对象;weak_ptr指向shared_ptr管理的对象,但是一种弱引用

shared_ptr<string> p;//指向string的指针

make_shared函数能够产生指向对象的shared_ptr

auto p = make_shared<string>(“abc”);//p为shared_ptr

当shared_ptr关联的对象增加引用、拷贝、作为返回值时,引用计数会增加;当引用减少、更改、销毁时,引用计数会减少;引用计数为0则该对象会被自动释放。
77.使用关键字new进行动态分配,在申请内存空间后,new返回一个指向该对象的指针。使用直接初始化方式或传统的构造方式(圆括号)对初始值有不同的影响。

string *s1 = new string;//空字符串
string *s2 = new string();//空字符串
int *i1 = new int;//随机值
int *i2 = new int();//0

当内存用尽时,new会抛出异常bad_alloc。使用new(nothrow)组织抛出异常,返回一个空指针。
78.使用关键字delete删除内存

int *p(new int(42));
auto q=p;//指针q指向p指向的内容
delete p;//删除p指向的内容
p= nullptr;//p绑定为空
q=nullptr;//更改p对q没有作用,还需要对q更改绑定

79.混用智能指针和普通指针可能会产生严重后果

void process(shared_ptr<int> ptr){};//在process执行后,产生的局部ptr会被自动释放
    int *x(new int(1024));//普通指针
    process(x);//错误,不能将普通指针转换为智能指针
    process(shared_ptr<int>(x));//语法正确,但是执行完后x将被释放
    int j=*x;//错误,x指向的对象已被释放

使用智能指针在遭遇异常时,即使未被捕获,该对象依然可被自动释放,而使用普通指针在手动释放前遭遇异常,该对象将不会被释放。
80.使用reset更改shared_ptr的绑定对象

if(!p.unique()) p.reset(new string(*p));//p不是唯一用户,重新绑定到值的拷贝
*p=newVal;//更改绑定的值

81.shared_ptr绑定的不是new分配的内存时,传递一个删除器使得指针在销毁时调用删除器,因为此时销毁不会调用delete

void end_function(string &s){};
shared_ptr<string> p(&s,end_function);//end_function中完成对shared_ptr的释放

82.unique_ptr不能拷贝或赋值,但可通过release()和reset()转移所有权。release将指针置空,并返回指向原对象的指针;reset时若原指针不为空,则将原指向对象释放后指向新的对象
83.weak_ptr指向由shared_ptr指向的对象,但是weak_ptr不增加引用计数,所以访问weak_ptr的对象时可能为空,必须提前判断
84.使用new创建数组后,用delete []释放内存。可使用unique_ptr管理数组,自动释放内存,如果想使用shared_ptr管理数组,必须自定义删除器
85.使用new初始化数组时,添加()可以对对象类型进行默认值初始化。可以在初始化时设置申请空间为一个变量,当该变量为0时,语法依然通过,但是该指针不能解引用,类似于尾后指针。

int *p1=new int[10];//未初始化
int *p2=new int[10]();//初始化默认值为0
char *p3=new char[0];//p3无法调用

86.allocator解决new浪费内存的问题,它定义在memory头文件中,先分配原始未构造的内存,然后在使用时构造内存,结束后销毁内存。

allocator<string> alloc;
auto const p = alloc.allocate(n);//分配n个string内存
auto q=p;//指向最后构造的元素之后的位置
alloc.construct(q++);//空字符串
alloc.construct(q++,10,’c’);//cccccccccc
alloc.construct(q++,”hi”);//hi
cout<<*p<<endl; 输出空字符串
cout<<*q<<endl; 指向未构造的内存,未定义
while(p!=q)
    alloc.destory(--q); 从后向前销毁
alloc.deallocate(p,n); 将未构造的内存返还系统

87.拷贝和填充未初始化内存

vector<string> v={“1”,”2”};
auto p=alloc.allocate(v.size()*2);//分配空间为v的两倍
auto q=uninitialized_copy(v.begin(),v.end(),p);//拷贝元素用于构造内存
uninitialized_fill_n(q,v.size(),42);//剩余元素全部初始化为42
//Sales_data s=1.0;//不使用explicit可以这么调用,但是阅读性大大降低

留下回复

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据