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

1.合成的默认构造函数
定义:

编译器自己创建的默认构造函数又被称作合成的默认构造函数。
Note:

只有当类没有声明任何构造函数时,编译器才会自动地生成,默认构造函数。
2.默认构造函数(自定义)
定义:

不接受任何实参的构造函数

//我们需要定义默认构造函数
Person::Person() { }

C++ 11 default 关键字,构造函数后加这个关键字,来要求编译器生成构造函数。

//类外定义 默认不是内联
 
Person::Person() = default;
 
//类内定义 默认是内联
 
Person() = default;

Note:

如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数的初始化列表为这些成员提供初始值。成员初始化顺序,与类定义中的出现顺序一致。所以我们的初始化列表中的先后顺序要尽量和类中中成员变量的声明顺序相同。

#include <iostream>
#include <string>
class Person
{
    friend void getID1(Person p);
public:
    Person() = default;
    void getID2()
    {
        std::cout << this->id << std::endl;
    }
    Person(int id_);
    ~Person();
private:
    int id;
    const int id2;
    int& id3;
    std::string name;
};
Person::Person(int id_) : id(id_), id2(id_), id3(id)//不在这里初始化会报错
{
}
Person::~Person()
{
}
void getID1(Person p)
{
    Person pp = p;
    std::cout << pp.id << std::endl;
}
int main(int, char**)
{
    Person p1(10);
    p1.getID2();
    getID1(p1);
}

对于静态的成员变量,必须在类外进行初始化。

默认实参和构造函数,如果一个构造函数为所有参数都提供了默认实参,则实际上也定义了默认构造函数。

#include <iostream>
#include <string>
class Person
{
public:
    int id;
    std::string name;
    //Person() = default;//与下面的默认构造函数会冲突
    Person(std::string s = "hello", int id_ = 0) : name(s), id(id_)
    {
 
    }
    ~Person();
};

3.委托构造函数(delategating constructor)
定义:

一个委托构造函数使用他所属的类的其他构造函数执行他自己的初始化过程,或者说把他的一些(或全部)职责委托给了其他构造函数
实例:

class Person
{
public:
    int id;
    std::string name;
    Person(std::string s, int id_ ) : name(s), id(id_)
    {
    }
    Person() : Person("", 0){}//默认构造函数
    Person(std::string s) : Person(s, 0){}//一个参数的构造函数
    ~Person();
};

4.constexpr 构造函数
前言
知识补充1:

字面值:是一个不能改变的值,如数字、字符、字符串等。单引号内的是字符字面值,双引号内的是字符串字面值。

字面值类型:算术类型,引用,指针 。自定义类、IO类不属于该类型。值得注意的是,一个形如42 值被称作字面值常量。类似42 这样的值,在程序中被当作字面值常量。称之为字面值是因为只能用它的值称呼它, 称之为常量是因为它的值不能修改。
知识补充2:

常量表达式(const experssion):是指(1)值不会改变 并且 (2)在编译过程就能得到计算结果的表达式。字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。

一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。我们一起来看几个例子。

const int a = 1; //常量表达式
const int b = a + 1; //常量表达式
int c = 2; //初始值是字面值常量,但c数据类型是普通int。所有c 不是常量表达式。
int fun()
{
    int a = 100;
    return a;
}
const int d = fun(); //fun()值要在运行时得到,d不是字面值常量

知识补充3:

constexpr 用于验证一个变量是否是常量表达式。如果不是则会报错。

const int a = 1; //常量表达式
const int b = a + 1; //常量表达式
int c = 2; //初始值是字面值常量,但c数据类型是普通int。所有c 不是常量表达式。
constexpr int d = c;//error
constexpr int f = b;//ok
constexpr int e = f;//ok
f = 100;//error : assignment of read-only variable ‘f’GCC

constexpr 函数是指能用于常量表达式的函数。要求:返回值类型,参数类型都必须是字面值类型有且只有一条rerurn 语句。

constexpr修饰的函数,当传入参数是可以在编译期计算出来时,产生constexpr变量;

当传入参数不可以在编译期计算出来时,产生运行期遍历(constexpr等于不存在)。

//众所周知,array的size是需要在编译期确定的,所以当其size不是一个常量表达式时,是无法通过编译的
constexpr int foo(int i)
{
    return i + 5;
}
 
int main()
{
    int i = 10;
    std::array<int, foo(5)> arr; // OK,5是常量表达式,计算出foo(5)也是常量表达式
     
    foo(i); // Call is Ok,i不是常量表达式,但仍然可以调用(constexpr 被忽略)
     
    std::array<int, foo(i)> arr1; // Error,但是foo(i)的调用结果不是常量表达式了
    
}

定义:

constexpr 构造函数 的函数体是空的,并通过前置关键字constexpr来声明。
知识点补充4:

聚合类:
用户可以直接访问成员,并且具有特殊的初始化语法形式。需要满足的特点:

所有的成员都是public
没有定义任何构造函数
没有类内初始值
没有基类,也没有virtual 函数
struct Dada
{
    int value;
    string s ;
}
Data val1 = {0, “helloworld”};

知识点补充5:

字面值常量类:
1.数据成员都是字面值类型的聚合类。
2.如果不是聚合类,则需要符合下面要求

数据成员都必须是字面值类型
类中至少包含一个constexpr 构造函数
如果一个数据成员含有类内初始值,该初始值必须是常量表达式;或者如果属于某种类类型,则初始值必须使用自己的constptr 函数。
类必须使用析构函数的默认定义,该成员负责销毁类的对象。

Note:

一个字面值常量类必须至少提供一个constexpr构造函数。
实例:

#include <iostream>
class Debug
{
public:
    constexpr Debug(bool b) : hw(b), io(b), other(b)
    {
 
    }
    constexpr Debug(bool a, bool b, bool c) : hw(a), io(b), other(c)
    {
 
    }
    // constexpr 函数是指能用于常量表达式的函数。要求:返回值类型,参数类型都必须是字面值而类型
    // 有且只有一条rerurn 语句。
    constexpr bool any() const
    {
        return hw || io || other;
    }
    void SetHw(bool a)
    {
        hw = 0;
    }
    void SetIo(bool b)
    {
        io = b;
    }
    void SetOther(bool c)
    {
        other = c;
    }
private:
    bool hw;
    bool io;
    bool other;
};
int main()
{
    constexpr Debug io_sub(false, true, false);
    if (io_sub.any())
    {
        std::cerr << "print approiate error messages" <<std::endl;
    }  
    constexpr Debug proad(false);
    if(proad.any())
    {
        std::cerr <<  "print an error message" <<std::endl;
    }
}

constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数,或者是一条常量表达式。这样声明以后,就可以在使用constexpr表达式或者constexpr函数的地方使用字面值常量类了。
5.复制构造函数
定义:

复制构造函数 通过从同一类型的对象复制成员值来初始化对象。 如果类成员是所有简单类型(如标量值),则编译器生成的复制构造函数便已足够,无需自行定义。 如果你的类需要更复杂的初始化,则需要实现自定义复制构造函数。 例如,如果类成员是一个指针,则需要定义一个复制构造函数以分配新内存并从另一个指向对象复制值。 编译器生成的复制构造函数只复制指针,使新指针仍指向另一个内存位置。
Note:

如果不声明复制构造函数,编译器将为你生成 member-wise 复制构造函数。 如果不声明复制赋值运算符,编译器将为你生成 member-wise赋值重载运算符。 声明复制构造函数不会取消编译器生成的赋值重载运算符,反之亦然。 如果实现上述其中一项,建议你还实现另一项以使代码的含义变得明确。

//自定义复制构造函数
Box(Box& other); // Avoid if possible--allows modification of other.
Box(const Box& other);
Box(volatile Box& other);
Box(volatile const Box& other);
 
// Additional parameters OK if they have default values
Box(Box& other, int i = 42, string label = "Box");
实例:
#include <iostream>
// #include <functional>
class  Person
{
public:
    Person() = default;
    Person(int id, std::string name) : id_(id), name_(name)
    {
    } 
    Person(const Person&);
    void operator=(const Person& x);
private:
    int id_;
    std::string name_;
};
void Person::operator=(const Person& x)
{
        if(this == &x)
        {
            return;
        }
        id_ = x.id_;
        name_ = x.name_;
        std::cout << "copy = constructor" << std::endl;
}
Person::Person(const Person& p)
{
    id_ = p.id_;
    name_ = p.name_;
    std::cout << "copy constructor" << std::endl;
}
int main()
{
    Person p1(1, "xiaoming");
    Person p2 = p1;//导致复制构造函数的调用
    p2 = p1;//赋值重载运算符
    Person p3(p2);//导致复制构造函数的调用
}

运行结果:

copy constructor
copy = constructor
copy constructo

若用户没有定义自己的复制构造函数,系统会自动生成一个复制构造函数,其作用是将参数的之赋予当前的对象。复制构造函数只能有一个,因为复制构造函数的参数只能是当前类的一个对象,参数表是固定的,无法重载。从运行结果来看, Person p2 = p1; Person p3(p2);都会调用复制构造函数。 p2 = p1;则不会,它调用的是自定义的,或者编译器给出的赋值重载运算符。

为应对连续赋值这样重载赋值运算符函数应该这样写:

Person& Person::operator=(const Person& x)
{
    if(this == &x)
    {
        return;
    }
    id_ = x.id_;
    name_ = x.name_;
    std::cout << "copy = constructor" << std::endl;
    return *this;
}
int main()
{
    Person p1(1, "xiaoming");
    Person p3;
    Person p2;
    p2 = p3 = p1;//此处如果重载赋值运算符没有return *this 会error。
}

运行结果:

copy = constructor
 
copy = constructor

深copy 与 浅copy 的深入

前文已经提到对于成员中包含指针的情况,如果我们不去定义,编译器默认定义不会去申请新的内存,两个对象的指针指向同一个内存单,这样就是浅copy,那么剩下的那个指针将会变成野指针。

实例

#include <iostream>
class  Animal
{
public:
     Animal() = default;
     Animal(int* p)
     {
         id_ = p;
     }
     Animal(const Animal& am)
     {
        id_ = new int();//深copy
        *id_ = *am.id_;
        //id = am_.id; //浅copy (反例)
        std::cout << "copy constructor is called" <<std::endl;
    }
    ~ Animal();
    int* id_ = nullptr;
};
 Animal::~ Animal()
{
    std::cout << "The destructor is called" << std::endl;
    delete id_;//释放所指向的内存
}
Animal* test1()
{
    int*  number = new int(100);
    Animal a (number);
    Animal* a1 = new Animal(a);
    return a1;
}
int main(int argc, char* argv[])
{
    Animal* am2 = test1();
    std::cout << *(am2->id_) << std::endl;
    delete am2;
}

深copy运行效果:
copy constructor is called
 
The destructor is called
 
100
 
The destructor is called

浅copy运行效果:

D:\file\C++\study_demo\demo02>g++ main.cpp
 
 
D:\file\C++\study_demo\demo02>a.exe
 
copy constructor is called
 
The destructor is called
 
7019088
 
The destructor is called

6.继承的构造函数
定义:

派生类可以通过使用声明从直接基类继承构造函数 。所使用的关键字是using 。
Note:

使用using继承基类的构造函数,不会继承默认,拷贝,和移动构造。 一般而言,当派生类未声明新数据成员或构造函数时,最好使用继承构造函数。
实例:

#include <iostream>
using namespace std;
 
class Base
{
public:
    Base() { cout << "Base()" << endl; }
    Base(const Base& other) { cout << "Base(Base&)" << endl; }
    explicit Base(int i) : num(i) { cout << "Base(int)" << endl; }
    explicit Base(char c) : letter(c) { cout << "Base(char)" << endl; }
 
private:
    int num;
    char letter;
};
 
class Derived : Base
{
public:
    // 继承所有的构造的函数从 Base
    using Base::Base;
 
private:
    // Can't initialize newMember from Base constructors.
    int newMember{ 0 };
};
 
int main()
{
    cout << "Derived d1(5) calls: ";
    Derived d1(5);
    cout << "Derived d1('c') calls: ";
    Derived d2('c');
    cout << "Derived d3 = d2 calls: " ;
    Derived d3 = d2;
    cout << "Derived d4 calls: ";
    Derived d4;
}

运行结果:

Derived d1(5) calls: Base(int)
Derived d1('c') calls: Base(char)
Derived d3 = d2 calls: Base(Base&)
Derived d4 calls: Base()

7 explicit 修饰构造函数
用来修饰只有一个参数的构造函数,只允许它显示调用,不允许隐式调用。

#include<cstring>
#include<string>
#include<iostream>
 
 
class Explicit
{
    private:
 
    public:
        explicit Explicit(int size)
        {
            std::cout << " the size is " << size << std::endl;
        }
        Explicit(const char* str)
        {
            std::string _str = str;
            std::cout << " the str is " << _str << std::endl;
        }
 
        Explicit(const Explicit& ins)
        {
            std::cout << " The Explicit is ins" << std::endl;
        }
 
        Explicit(int a,int b)
        {
            std::cout << " the a is " << a  << " the b is " << b << std::endl;
        }
};
 
int main()
{
    Explicit test0(15);
    Explicit test1 = 10;// 隐式调用Explicit(int size) 此时有explicit关键字修饰构造函数 error
 
    Explicit test2("RIGHTRIGHT");
    Explicit test3 = "BUGBUGBUG";// 隐式调用Explicit(const char* str)
 
    Explicit test4(1, 10);
    Explicit test5 = test1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值