learn-C++-1

利用暑假时间将C++从头到尾认真学习一下,特别是指针、面向对象、泛型等特性!
参考书籍为《C++ Primer Plus》

拷贝构造函数

拷贝构造函数用于将一个对象复制到新创建的对象中,一般会在以下情形被调用:

  1. 使用已初始化对象赋值新创建的对象;
  2. 对象作为函数参数传递
  3. 对象从函数中返回

拷贝构造函数分为浅拷贝和深拷贝,浅拷贝包括默认的,可以理解为仅复制,对于指针成员,新复制的并没有拥有新空间,与原者指向同处,这就会出现问题,当原者调用析构函数后会把该空间释放!而深拷贝则会创建一片新空间,本文的所有例子基于Person类(基类)和Worker类(扩展类)进行说明,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#Person类 person.h
#include <iostream>
class Person{
private:
char *name;//姓名
int age;//年龄
char *city;//所在地
char sex;//性别
public:
//构造函数
Person(const char *n,const int a,const char *c,const char s);
//析构函数
~Person();
//get
char * getName() const{return name;}
int getAge() const{return age;}
char * getCity() const{return city;}
char getSex() const{return sex;}
//set
void setName(const char *n);
void setAge(const int a);
void setCity(const char *c);
void setSex(const char s);
};

person.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "person.h"
#include <cstring>
#include <string>
//构造函数
Person::Person(const char *n,const int a,const char *c,const char s){
name=new char[std::strlen(n)+1];
std::strcpy(name,n);
age=a;
city=new char[std::strlen(c)+1];
std::strcpy(city,c);
sex=s;
}
//析构函数
Person::~Person(){
std::cout<<name<<" Bye Person!"<<std::endl;
delete []name;
delete []city;
}
//set
void Person::setName(const char *n){
name=new char[std::strlen(n)+1];
std::strcpy(name,n);
}
void Person::setAge(const int a){
age=a;
}
void Person::setCity(const char *c){
c=new char[std::strlen(c)+1];
std::strcpy(city,c);
}
void Person::setSex(const char s){
sex=s;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#Worker类 worker.h
#include "person.h"
class Worker:public Person{//公有继承 is-a
private:
int salary;//工资
char *workplace;//工作地点
public:
//构造函数
Worker(const char *n,const int a,const char *c,const char s,const int sl,const char *wp);
//析构函数
~Worker();
//get
int getSalary() const{return salary;};
char *getWorkplace() const{return workplace;};
//set
void setSalary(const int sl);
void setWorkplace(const char *wp);
};

下面来看看默认的浅拷贝:

1
2
Person p1=Person("Xiao Hong",8,"LinYi",'F');
Person p2=p1;

(注意这里显式调用构造函数也会触发拷贝构造函数,但是编译器优化掉了,在本篇文章后面会进行说明)会发现结果如下:

1
2
Xiao Hong Bye Person!
8 Bye Person!

p2在析构时name其实已经释放了

此时我们采用深拷贝,在person.h中添加以下声明:

1
Person(const Person &person);

person.cpp中如下扩展:

1
2
3
4
5
6
7
8
9
Person::Person(const Person &person){
name=new char[std::strlen(person.name)+1];
std::strcpy(name,person.name);
age=person.age;
city=new char[std::strlen(person.city)+1];
std::strcpy(city,person.city);
sex=person.sex;
std::cout<<"深拷贝构造函数"<<std::endl;
}

注意这里要传引用!

然后结果会变为:

1
2
3
深拷贝构造函数
Xiao Hong Bye Person!
Xiao Hong Bye Person!

下面介绍之前说的那三种使用场景。

使用已初始化对象赋值新创建的对象

上面那个例子中用到的,还有就是继承中,如下所示:

1
2
3
Person p1=Worker("Xiao Ming",10,"LinYi",'M',500000,"Ali");
cout<<p1.getName()<<endl;
p1.setName("Xiao Song");

这个时候也会调用拷贝构造函数,执行结果为:

1
2
3
4
5
深拷贝构造函数
Xiao Ming Bye Worker!
Xiao Ming Bye Person!
Xiao Ming
Xiao Song Bye Person!

由此推断执行顺序应该是:

生成Worker对象—>深拷贝生成新Person对象—>Worker析构—>基类Person析构—>拷贝后的Person析构

对象作为函数参数传递

定义函数printObj:

1
2
3
4
5
6
7
8
void printObj(const Person p){
cout<<"print "<<p.getName()<<endl;
}
int main(){
Person p=Person("Xiao Ming",10,"LinYi",'M');
printObj(p);
return 0;
}

执行结果为:

1
2
3
4
深拷贝构造函数
print Xiao Ming
Xiao Ming Bye Person!
Xiao Ming Bye Person!

对象从函数中返回

定义函数getObj:

1
2
3
4
5
6
7
8
9
Person getObj()
{
Person p=Person("Xiao Ming",10,"LinYi",'M');
return p;
}
int main(){
getObj();
return 0;
}

但是没有预想到调用深拷贝构造函数,查阅博文知是编译器进行了优化,要加-fno-elide-constructors选项

加上后神奇的一幕发生了!原本Person p=Person("Xiao Ming",10,"LinYi",'M'); 这种显示调用构造函数也会调用深拷贝,结果如下:

1
2
3
深拷贝构造函数
Xiao Ming Bye Person!
Xiao Ming Bye Person!

所以有两条析构,而隐式调用则没有Person p1("Xiao Ming",10,"LinYi",'M');

然后调用getObj的结果如下所示:

1
2
3
4
5
深拷贝构造函数
Xiao Ming Bye Person!
深拷贝构造函数
Xiao Ming Bye Person!
Xiao Ming Bye Person!