C++笔记(6)

1.相关数据结构成员方法的调用

class Stack
{
public:
     Stack()
     {
          cout << this << endl;
          cout << "Stack()" << endl;
          mpstack = new int[10];
          mtop = -1;
          msize = 10;
     }
     //explicit:你明确的调用了这个构造函数,我才给你构造对象;否则
     //编译错误   explicit:防止隐式产生临时对象用的!
     //explicit Stack(int size)
     Stack(int size)
     {
          cout << this << endl;
          cout << "Stack(int)" << endl;
          mpstack = new int[size];
          mtop = -1;
          msize = size;
     }
     Stack(int size, int len, int initVal)
     {
          cout << this << endl;
          cout << "Stack(int, int, int)" << endl;
          mpstack = new int[size];
          msize = size;
          mtop = -1;
          for (int i = 0; i < len; ++i)
          {
              mpstack[++mtop] = initVal;
          }
     }
     ~Stack()
     {
          cout << this << endl;
          cout << "~Stack()" << endl;
          delete[]mpstack;
          mpstack = NULL;
     }
     Stack(const Stack &src)
     {
          cout << &src << "=>" <<this << endl;
          cout << "Stack(const Stack &src)" << endl;
          mpstack = new int[src.msize];
          msize = src.msize;
          mtop = src.mtop;
          for (int i = 0; i <= mtop; ++i)
          {
              mpstack[i] = src.mpstack[i];
          }
     }
     void operator=(const Stack &src)
     {
          cout << &src << "=>" << this << endl;
          cout << "void operator=(const Stack &src)" << endl;
          if (this == &src)
              return;

          delete[]mpstack;

          mpstack = new int[src.msize];
          msize = src.msize;
          mtop = src.mtop;
          for (int i = 0; i <= mtop; ++i)
          {
              mpstack[i] = src.mpstack[i];
          }
     }
private:
     int *mpstack;
     int mtop;
     int msize;
};
在main()函数里:
Stack s1;//调用默认的构造函数
Stack s2(20);//调用带有一个整型参的构造函数
Stack s3(10,20,15);//调用带有三个整型参的构造函数
Stack s4(s3);//Stack s4=s3;//调用自定义的拷贝构造函数
s2=s4;//调用自定义的赋值运算符的重载函数
1)Stack s5=Stack(20);//Stack s5=20;//Stack s5(20);//直接调用带有一个整型参的构造函数
     Stack s6=Stack(20,15,15);//直接调用带有三个整型参的构造函数
     //告诉编译器,帮我产生一个临时对象【这里指定了类名称,叫做显示产生临时对象】
     临时对象的生存周期:语句结束,周期就到了。
/*
C++编译器的编译规则:
用临时对象[显示产生的临时对象]拷贝构造函数时,临时对象就被优化掉了,直接构造左值对象就行
*/
2)Stack s7=(Stack)(20,15,30);//Stack s7=(Stack)30;//Stack s7=Stack(30);
                                 //此处是逗号表达式,最终值为30
     //调用带有一个整型参的构造函数
3)s7=20;//s7.operator=(20);//此处相当于隐式产生临时对象
     此句话相当于做了三件事:
     1.Stack(int)==>20,即隐式产生临时对象;
     2.operator=(const Stack &src(src引用的就是临时对象)),即调用赋值运算符的重载函数;
     3.析构临时对象.
问题:
隐式产生的临时对象为什么不优化?
一般隐式的临时对象不是在新对象的创建的情况下产生的,而是在对已存在的对象进行赋值时产生的,而C++的编译规则中临时对象被优化的前提是用临时对象拷贝构造新对象,所以隐式产生的临时对象的创建是不能被优化从而省略掉的,而是在等号赋值之后会被析构。
运算符explicit:意为明确的,确定的
                          即你明确的调用了这个构造函数,我才给你构造,否则编译报错。
                          只能修饰构造函数,是防止隐式产生临时对象用的。
                          eg:s7=20;此时隐式产生了临时对象,并且调用的是带有一个整型参的构                                   造函数,所以我们就可以给带有一个整型参的构造函数之前加上                                           explicit运算符,此处就不会隐式产生临时对象了。
                                 即:explicit Stack(int size);
4)Stack *p1=new Stack();
     Stack *p2=new Stack(10);
     Stack *p3=new Stack(10,5,60);
     //在堆上构造新对象,必须自己delete,否则永远不会析构
     delete p1;
     delete p2;
     delete p3;
     异常:
     delete (int *)p1;
     delete (int *)p2;
     delete (int *)p3;
     //指针的类型会影响析构的调用,指针调用什么类型,析构什么类型,而这里delete时起始地址没变,所以会释放p1/p2/p3所占用的整块内存,但是不会调用类类型的析构,所以会将p1/p2/p3占用的外部资源弄丢了

     Stack *p4=new Stack[3];
     //在堆上创建一个对象数组,那么每个对象就只能调用默认构造函数,此处调用三次
     delete []p4;
     //delete三次
     异常:
     delete p4;
     //整块内存都释放了,但却只释放了Stack[0]上的外部资源,没有释放Stack[1],Stack[2]上的外部资源
     new:除了开辟内存,还能调用对象的构造函数
     delete:会把堆上的对象先析构,再释放其内存
5)Stack *p5=&Stack(10);//栈上构造
     //用指针指向临时对象,语句结束,临时对象就析构了
     Stack &ref1=Stack(20);//Stack ref1(20);//栈上构造
     //用引用来引用临时对象,相当于起别名,即使语句结束,也不会析构
2.例题:请给出下面对象创建过程中涉及的方法打印
class Test
{
public:
     Test(int a = 5, int b = 5) :ma(a), mb(b)
     {
          cout << "Test(int, int)" << endl;
     }
     ~Test()
     {
          cout << "~Test()" << endl;
     }
     Test(const Test &src) :ma(src.ma), mb(src.mb)
     {
          cout << "Test(const Test&)" << endl;
     }
     void operator=(const Test &src)
     {
          ma = src.ma; mb = src.mb; cout << "operator=" << endl;
     }
private:
     int ma;
     int mb;
};
Test t1(10, 10);//程序运行时开辟,调用带有两个整型参的构造函数
int main()
{
     Test t2(20, 20);//调用带有两个整型参的构造函数
     Test t3 = t2;//调用自定义的拷贝构造函数
     static Test t4 = Test(30, 30);//t4起初开辟内存,在运行时调用拷贝构造函数
     t2 = Test(40, 40);
     //1.调用带有两个整型参(20,20)的构造函数构造临时对象  2.赋值运算符的重载函数  3.析构临时对象
     t2 = (Test)(50, 50);//相当于强转逗号表达式,相当于t2=Test(50,5)[5是默认大小]
     //1.调用带有两个整型参(20,20)的构造函数构造临时对象  2.赋值运算符的重载函数  3.析构临时对象
     t2 = 60;//相当于隐式产生一个临时对象,等价于t2=Stack(60,5)
     //1.调用带有两个整型参(20,20)的构造函数构造临时对象  2.赋值运算符的重载函数  3.析构临时对象
     Test *p1 = new Test(70, 70);//动态开辟对象的内存,调用带有两个整型参的构造函数
     Test *p2 = new Test[2];//动态开辟对象的内存,调用带有两个默认的构造函数
     //堆上动态开辟的内存必须手动delete,否则永远不会析构
     Test *p3 = &Test(80, 80);
     //指针:1.调用带有两个整型参的构造函数构造一个临时对象  2.语句结束临时对象析构
     Test &p4 = Test(90, 90);
     //引用:调用带有两个整型参的构造函数构造一个临时对象,并给这个临时对象起了个别名p4,所以语句结束临时对象不会析构
     delete p1;//手动释放p1所占用的外部资源
     delete[]p2;//手动释放p2所占用的外部资源
}
Test t5(100, 100);//程序运行时开辟,调用带有两个整型参的构造函数
//释放的顺序是:p4,t3,t2,t4,t5,t1

运行结果为:
3.问题:
   C语言中空struct结构体的sizeof大小是0,而C++中空class类的sizeof大小是1个字节?
   struct结构体里只算变量的大小,存在变量,按照字节对齐原理计算字节大小,当结构为空时,就是没有变量存在,所以sizeof(struct)=0;
   而在类class中,计算的是一个类的实例化对象所占空间的大小。每个实例在内存中都有独一无二的地址,为了达到这个目的,编译器往往会给空类隐含的加一个字节,这样空类在实例化后在内存中得到了独一无二的地址,所以空类所占的内存大小是1个字节。
   /*
        类的大小:
        1.为类的非静态成员数据的类型大小之和;
        2.由编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针);
        3.为了优化存取效率,进行的边缘调整(字节对齐);
        4.与类中的构造函数,析构函数以及其他的成员函数无关。
   */
4.关于函数代码优化:
class Test
{
public:
     Test(int a = 5) :ma(a)
     {
          cout << "Test(int=5)" << endl;
     }
     ~Test()
     {
          cout << "~Test()" << endl;
     }
     Test(const Test &src) :ma(src.ma)
     {
          cout << "Test(const Test&)" << endl;
     }
     void operator=(const Test &src)
     {
          ma = src.ma;
          cout << "operator=" << endl;
     }
     int getValue(){ return ma; }
private:
     int ma;
};  

第一种优化:
如果用值传递相当于产生了一个临时对象temp,Test a=temp;会调用拷贝构造函数,而在函数结束后还要析构临时对象,如果引用接收对象,就只是给本来的对象起了个别名,不会产生临时对象,更不用析构,提高代码效率。
优化一:函数参数传递过程中,要用引用接收对象,不要用值接受对象。
第二种优化:
如果返回已经存在的对象,还要在之前调用拷贝构造新函数先构造这个对象再返回它,函数结束之后再析构这个对象,如果直接返回临时对象,就不存在拷贝构造和析构的过程了,提高代码效率。
优化二:当函数返回一个对象时,要返回临时对象,不要返回已经存在的对象。
第三种优化:
以初始化的方式接收的话就直接调用构造函数构造t2,不会有临时对象的产生,此时代码效率达到最优。
优化三:以初始化的方式接收返回类型为对象的函数调用。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章