cpp创建对象的多种形式

一 创建对象的几种方式

1 使用非默认构造函数来创建对象的几种形式

1.1、显示调用

Person person = Person("binny1", 26);

这种方式创建对象,C++标准允许编译器使用两种方式来执行:

1、会创建一个临时对象,然后将临时对象复制到person中,并丢弃临时对象。此时,将为临时对象调用析构函数,正如上面的结果。

编译器肯能立即删除临时对象,也可能过一会儿。

1.2、隐式调用

Person person2("binny2", 27);//会创建临时对象

这种格式更紧凑,它与显示调用等价。

1.3、构造函数与new关键字一起使用

Person *pPerson3 = new Person("binny3", 8);//不会创建临时对象

这条语句创建一个**Person对象,将其初始化为参数列表提供的值,并将该对象的地址赋给pPerson**指针。

这种情况下对象没有名字,但可以使用指针来管理对象。

1.4、列表初始化

如果编译器支持C++11,则可以用时列表初始化。只要提供的大括号列表中的数据与某个构造函数的参数列表匹配就可以。

Person person4 = {"binny4", 26};//c++11,会创建临时对象
Person person5{"binny5", 26};//c++11,会创建临时对象
Person *pPerson6 = new Person{"binny6", 26};//c++11,不会创建临时对象
Person person{};//与默认构造函数相匹配

2、使用默认构造函数来创建对象

2.1、显示调用

Person person = Person();

2.2、隐式调用

Person person;

隐式调用默认构造函数,不要使用**圆括号**。

案例源码

头文件

//
// Created by Biter on 2019/10/16.
//
#include <iostream>

#ifndef NDK_PERSON_H
#define NDK_PERSON_H

using namespace std;

class Person {

private:
    char *mName;
    int mAge;
public:
    Person();

    Person(int age);

    Person(char *name, int age);

    void setName(char *name);

    void setAge(int age);

    char * getName();

    int getAge();

    ~Person();
};

#endif //NDK_PERSON_H

头文件的实现

//
// Created by Biter on 2019/10/16.
//

#include "../includes/Person.h"

Person::Person() {
    mName = "biter";
    mAge = 26;
}

Person::Person(char *name, int age) {
    mName = name;
    mAge = age;
    cout << "Hello  " << mName << endl;
}

Person::Person(int age) {
    mAge = age;
}

void Person::setName(char *name) {
    mName = name;
}

void Person::setAge(int age) {
    mAge = age;
}

int Person::getAge() {
    return mAge;
}

char *Person::getName() {
    return mName;
}

Person::~Person() {
    cout << "Bey  " << mName << endl;
}```
## 项目代码

```c
//
// Created by Biter on 2019/10/15.
//
#include <iostream>
#include "includes/Person.h"

int main() {

    //第一种创建对象的方式
    Person person1 = Person("binny1,显示调用构造函数", 26);
//    //第二种创建对象的方式
    Person person2("binny2,隐式调用构造函数", 27);
//    //第三种创建对象的方式
    Person *pPerson3 = new Person("binny3,动态创建对象", 8);

    //c++11 列表初始化
    Person person4 = {"binny4,显示列表初始化", 26};
    Person person5{"binny5,隐式列表初始化", 26};
    Person *pPerson6 = new Person{"binny6,动态列表初始化", 26};

    std::cout << "person1 name = " << person1.getName() << std::endl;
    std::cout << "person2 name = " << person2.getName() << std::endl;
    std::cout << "person3 name = " << pPerson3->getName() << std::endl;
    std::cout << "person4 name = " << person4.getName() << std::endl;
    std::cout << "person5 name = " << person5.getName() << std::endl;
    std::cout << "person6 name = " << pPerson6->getName() << std::endl;

    Person person7 = 26;
    person7.setName("p9");
    std::cout << "p9.getAge() = " << person7.getAge() << std::endl;
    return 0;
}

运行结果

C++提供了多种对象的创建方式,每次创建对象时(包括使用new动态分配内存),C++都是用类的构造函数。

从运行结果看出,动态创建对象的时候,没有调用析构函数。

总结

小括号,老语法

不使用赋值符号=

Person person();//默认构造函数
Person person2("binny2", 27);//非默认构造函数

使用赋值符号=

右值指明数据类型

Person person1 = Person("binny1,显示调用构造函数", 26);
Person *pPerson3 = new Person("binny3,动态创建对象", 8);

大括号,新语法

不使用赋值符号=

Person person5{"binny5,隐式列表初始化", 26};

使用赋值符号=

Person person4 = {"binny4,显示列表初始化", 26};
Person *pPerson6 = new Person{"binny6,动态列表初始化", 26};

无括号

右值不指名数据类型

如果构造参数只有一个参数,则可以将对象初始化为一个与该构造器参数类型相同的值,此时该构造将被调用。

Person person7 = 26;
std::cout << "p9.getAge() = " << person7.getAge() << std::endl;


由于没有初始化mName,则析构函数被调用的时候输出乱码。修改如下:

Person person7 = 26;
p9.setName("p9");
std::cout << "p9.getAge() = " << person7.getAge() << std::endl;

二、补充形式

C++构造函数初始化列表

构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用**初始化列表**。

C++构造函数的初始化列表使得代码更加简洁。

#include <iostream>
using namespace std;

class Student{
private:
    char *m_name;
    int m_age;
    float m_score;
public:
    Student(char *name, int age, float score);
    void show();
};

//采用初始化列表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
    //TODO:
}
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}

int main(){
    Student stu("小明", 15, 92.5f);
    stu.show();
    Student *pstu = new Student("李华", 16, 96);
    pstu -> show();

    return 0;
}

如本例所示,定义构造函数时并没有在函数体中对成员变量一一赋值,其函数体为空(当然也可以有其他语句),而是在函数首部与函数体之间添加了一个冒号:,后面紧跟m_name(name), m_age(age), m_score(score)语句,这个语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;语句,也是赋值的意思。

使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了。

初始化列表可以用于全部成员变量,也可以只用于部分成员变量。下面的示例只对 m_name 使用初始化列表,其他成员变量还是一一赋值:

Student::Student(char *name, int age, float score): m_name(name){
    m_age = age;
    m_score = score;
}

注意,成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。请看代码:

#include <iostream>
using namespace std;
class Demo{
private:
    int m_a;
    int m_b;
public:
    Demo(int b);
    void show();
};
Demo::Demo(int b): m_b(b), m_a(m_b){ }
void Demo::show(){ cout<<m_a<<", "<<m_b<<endl; }
int main(){
    Demo obj(100);
    obj.show();
    return 0;
}
运行结果:
2130567168, 100

在初始化列表中,我们将 m_b 放在了 m_a 的前面,看起来是先给 m_b 赋值,再给 m_a 赋值,其实不然!成员变量的赋值顺序由它们在类中的声明顺序决定,在 Demo 类中,我们先声明的 m_a,再声明的 m_b,所以构造函数和下面的代码等价:

Demo::Demo(int b){
    m_a = m_b;
    m_b = b;
}

m_a 赋值时,m_b 还未被初始化,它的值是不确定的,所以输出的 m_a 的值是一个奇怪的数字;给 m_a 赋值完成后才给 m_b 赋值,此时 m_b 的值才是 100。

obj 在栈上分配内存,成员变量的初始值是不确定的。

初始化 const 成员变量

构造函数初始化列表还有一个很重要的作用,那就是初始化 const 成员变量。初始化 const 成员变量的唯一方法就是使用初始化列表。例如 VS/VC 不支持变长数组(数组长度不能是变量),我们自己定义了一个 VLA 类,用于模拟变长数组,请看下面的代码:

class VLA{
private:
    const int m_len;
    int *m_arr;
public:
    VLA(int len);
};
//必须使用初始化列表来初始化 m_len
VLA::VLA(int len): m_len(len){
    m_arr = new int[len];
}

VLA 类包含了两个成员变量,m_lenm_arr 指针,需要注意的是 m_len 加了 const 修饰,只能使用初始化列表的方式赋值,如果写作下面的形式是错误的:

class VLA{
private:
    const int m_len;
    int *m_arr;
public:
    VLA(int len);
};
VLA::VLA(int len){
    m_len = len;
    m_arr = new int[len];
}
posted @ 2022-03-26 16:58  浪客禅心  阅读(804)  评论(0编辑  收藏  举报