C++构造函数

构造函数

  • 用于类对象的初始化
  • 程序声明对象时,将自动调用构造函数
  • 只能用来创建对象,不能通过对象来调用

注:1. 不能将成员变量作为构造参数的参数名,因为会导致名称相同,无法赋值,例如:shares=shares
2. 常规的初始化方法(大括号的初始化)对于类对象而言是非法的,因为类的成员变量访问状态是私有的,所以C++提供了一个特殊的成员函数-类构造函数

基本操作

声明构造函数

  • 头文件
Stock(const string & co, long n=0, double pr=0.0);

定义构造函数

  • 源文件
Stock::Stock(const string & co, long n=0, double pr=0){
	this->company = co;
	this->share = n;
	this->share_val = pr;
}

使用构造函数

//第一种:显式
Stock food = Stock("World Cabbage", 250, 1.25);
//第二种:隐式
Stock garment("Furry Mason", 50, 2.5);
  • 与new一起使用
Stock *pstock = new Stock("Electroshock Games", 18, 19.0);
  • C++11列表初始化
    • 只要提供与某个构造函数的参数列表匹配的内容,并用大括号括起,就会自动调用相应的构造函数
//前三个使用创建的构造函数
Stock hot_tip = {"Derivatives Plus Plus", 100, 45.0};
Stock jock {"Sport Age Storage, Inc"};
Stock * jeff = new Stock{"Sport Age Storage, Inc", 200, 35.0};
//最后一个使用默认的构造函数
Stock temp {};

默认构造函数

  • 默认构造函数也可以自定义,来代替系统自动生成的

声明默认构造函数

  • 带参数的构造函数也可以是默认构造函数,只要所有的参数都有默认值
  • 如果要创建未初始化的对象Stock stock1,则必须定义一个不接受任何参数的默认构造函数
//两种方式
//第一种:给已有构造函数的所有参数提供默认值
Stock(const string & co = "ok", long n=0, double pr=0.0);
//第二种:通过函数重载来定义另一个构造函数——一个没有参数的构造函数
Stock();
  • 不要两种方式同时使用,只能存在一个默认构造函数
Klunk() { klunk_ct = n; }
Klunk(int n = 0) { klunk_ct = n; }
//以上会产生二义性,因为有两个默认构造函数,只能有一个

定义默认构造函数

//第一种:给已有构造函数的所有参数提供默认值
Stock::Stock(const string & co = "ok", long n=0, double pr=0.0)
{
	//...
}
//第二种:通过函数重载来定义另一个构造函数——一个没有参数的构造函数
Stock::Stock()
{
	//...
}

使用默认构造函数

//显式
Stock car = Stock();
//隐式
Stock car;

默认的默认构造函数

  • 当且仅当没有定义任何构造函数时,编译器才会提供默认的默认构造函数
  • 默认的默认构造函数没有参数,因为声明中不包含任何值,不执行任何操作
    • 对于使用内置类型的成员,默认的默认构造函数不对其进行初始化
    • 对于属于类对象的成员,则调用其默认构造函数

复制构造函数

  • 复制构造函数用于将一个对象复制到新创建的对象中
    • 用于初始化过程中,而不是常规的赋值过程中
  • 每当程序生成了对象副本或临时对象时,编译器都将使用复制构造函数(因为编译器优化,所以优先会找移动构造函数,找不到才用)
  • 如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制

声明复制构造函数

//声明
Class_name(const Class_name &);

定义复制构造函数

//定义
Class_name::Class_name(const Class_name &)
{
	//...
}

使用复制构造函数

  • 以下情况将使用复制构造函数
    • 将新对象初始化为一个同类对象
    • 按值将对象传递给函数
    • 函数按值返回对象
    • 编译器生成临时对象(优先找移动构造函数,找不到才用)
  • 下面的声明都将调用复制构造函数
StringBad ditto(motto);
StringBad metoo = motto;
StringBad also = StringBad(motto);
StringBad * pStringBad = new StringBad(motto);

默认复制构造函数

  • 如果没有定义复制构造函数,编译器将声明一个默认复制构造函数
  • 默认复制构造函数将逐个复制非静态成员(成员复制也是浅复制),复制的是成员的值
//以下代码等效
StringBad sailor = sports;
//********
sailor.str = sports.str
sailor.len = sports.len

移动构造函数(C++11)

  • 移动构造函数将右值对象通过右值引用和移动语义将原对象的所有权转移给目标对象
  • 复制构造函数可执行深复制,而移动构造函数只调整记录
  • 移动构造函数可能修改其实参,这意味着右值引用参数不应是const

声明移动构造函数

Class_name(Class_name &&);

定义移动构造函数

//定义
Class_name::Class_name(Class_name &&)
{
	//...
}

使用移动构造函数

#include <iostream>
using namespace std;
 
class demo{
public:
    demo():num(new int(0)){
        cout<<"construct!"<<endl;
    }
 
    demo(const demo &d):num(new int(*d.num)){
        cout<<"copy construct!"<<endl;
    }
 
    //添加移动构造函数
    demo(demo &&d):num(d.num){
        d.num = NULL;
        cout<<"move construct!"<<endl;
    }
 
    ~demo(){
        cout<<"class destruct!"<<endl;
    }
private:
    int *num;
};
 
demo get_demo(){
    return demo();
}
 
int main(){
    demo a = get_demo();
    return 0;
}
//输出结果
construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!
//1.程序开始,首先执行get_demo函数,创建匿名demo对象并返回
//2.返回值demo进行接收使用移动构造函数
//3.匿名demo被销毁
//4.对象a进行接收使用移动构造函数
//5.返回值demo被销毁
//6.程序执行完毕,对象a被销毁
  • 当类中同时包含复制构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用复制构造函数

默认移动构造函数

  • 如果没有提供移动构造函数,而代码又需要使用它,编译器将提供一个默认移动构造函数

复制与移动

  • 复制构造函数和移动构造函数将初始化分为两组
    • 使用左值对象初始化对象,将使用复制构造函数
    • 使用右值对象初始化对象,将使用移动构造函数

强制移动(C++11)

  • 移动构造函数和移动赋值运算符使用右值,如何让它们使用左值?
  • 例如:
Useless choices[10];
Useless best;
int pick;

best = choices[pick];
  • 由于choices[pick]是左值,因此上述赋值语句将使用复制赋值运算符,而不是移动赋值运算符

  • 如果能让choices[pick]看起来像右值,便可使用移动赋值运算符

  • 解决方法:

    1. 可使用运算符static_cast<>将对象的类型强制转换为Useless &&
      • best = static_cast<Useless &&>(choices[pick]);
    2. C++11可以使用头文件utility中声明的函数std::move()
      • best = std::move(choices[pick]);
      • 表达式std::move(choices[pick])是右值
        • 如果定义了移动赋值运算符,赋值语句将调用移动赋值运算符
        • 如果没有定义移动赋值运算符,编译器将使用复制赋值运算符
        • 如果也没有定义复制赋值运算符,将根本不允许上述赋值

委托构造函数(C++11)

  • 如果给类提供了多个构造函数,会有可能重复编写相同的代码,有些构造函数可能需要包含其他构造函数中已有的代码
  • C++11允许在一个构造函数的定义中使用另一个构造函数,这被称为委托,构造函数暂时将创建对象的工作委托给另一个构造函数
  • 委托使用成员初始化列表语法的变种
class Notes
{
private:
	int k;
	double x;
	std::string st;
public:
	Notes();
	Notes(int);
	Notes(int, double);
	Notes(int, double, std::string);
};

Notes::Notes(int kk, double xx, std::string stt) : k(kk), x(xx), st(stt)
{
    //...
}

Notes::Notes() : Notes(0, 0.01, "Oh") 
{
    //...
}

Notes::Notes(int kk) : Notes(kk, 0.01, "Ah")
{
    //...
}

Notes::Notes(int kk, double xx) : Notes(kk, xx, "Uh")
{
    //...
}
  • 上述默认构造函数使用第一个构造函数初始化数据成员并执行其函数体,然后再执行自己的函数体

继承构造函数(C++11)

  • C++11提供了一种让派生类能够继承基类构造函数的机制,将using声明使特定的标识符可用using用于构造函数
  • 这让派生类继承基类的所有构造函数(默认构造函数、复制构造函数和移动构造函数除外),但不会使用与派生类构造函数的特征标匹配的构造函数
class BS
{
private:
    int q;
    double w;
public:
    BS() : q(0), w(0){ }
    BS(int k) : q(k), w(100){ }
    BS(double x) : q(-1), w(x){ }
    BS(int k, double x) : q(k), w(x){ }
    void Show() const { std::cout<<q<<", "<<w<<'\n'; } 
};

class DR : public BS
{
    short j;
public:
    using BS::BS; //必须要写才能继承构造函数
    DR() : j(-100) { }
    DR(double x) : BS(2 * x), j(int(x)) { }
    DR(int i) : j(-2), BS(i, 0.5 * i) { }
    void Show() const { std::cout<<j<<", "; BS::Show(); }  
};

int main()
{
    DR o1;
    DR o2(18.81);   //使用DR(double)替代BS(double),因为派生类重新定义了这个构造函数
    DR o3(10, 1.8); //使用BS(int, double)
    return 0;
}
  • 由于没有构造函数DR(int, double),因此创建DR对象o3时,将使用继承而来的BS(int, double)
  • 注:继承的基类构造函数只初始化基类成员;如果还要初始化派生类成员,则应使用成员列表初始化语法
DR(int i, int k. double x) : j(i), BS(k, x) { }

构造函数使用new运算符

  • 如果构造函数里使用new来分配内存,一般来说析构函数将需要delete来释放内存
  • new和delete必须相互兼容。new对应于delete,new[]对应于delete[]
  • 如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。然而,可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的nullptr),这是因为delete可以用于空指针(无论是带中括号还是不带中括号的new)。
  • 应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象
  • 应定义一个复制赋值运算符,通过深度复制将一个对象复制给另一个对象

深度复制的好处:不会删除原有对象的属性值,从而使用new来创建新对象的属性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值