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
运行结果为:
![](https://i-blog.csdnimg.cn/blog_migrate/6a2c8f35bacdc47fdbe544831638ced2.png)
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;
};
![](https://i-blog.csdnimg.cn/blog_migrate/da9430b406b0dfd4ac0b4560027faef1.png)
第一种优化:
![](https://i-blog.csdnimg.cn/blog_migrate/b82ea0a862bd771304b6d36c743f2c4f.png)
如果用值传递相当于产生了一个临时对象temp,Test a=temp;会调用拷贝构造函数,而在函数结束后还要析构临时对象,如果引用接收对象,就只是给本来的对象起了个别名,不会产生临时对象,更不用析构,提高代码效率。
优化一:函数参数传递过程中,要用引用接收对象,不要用值接受对象。
第二种优化:
![](https://i-blog.csdnimg.cn/blog_migrate/4a2efa47b43a04fc817009b5b6c283d9.png)
如果返回已经存在的对象,还要在之前调用拷贝构造新函数先构造这个对象再返回它,函数结束之后再析构这个对象,如果直接返回临时对象,就不存在拷贝构造和析构的过程了,提高代码效率。
优化二:当函数返回一个对象时,要返回临时对象,不要返回已经存在的对象。
第三种优化:
![](https://i-blog.csdnimg.cn/blog_migrate/b0a6694b8b30728dd9c551351aa2000c.png)
以初始化的方式接收的话就直接调用构造函数构造t2,不会有临时对象的产生,此时代码效率达到最优。
优化三:以初始化的方式接收返回类型为对象的函数调用。