C++ 类的构造函数详解及实例

C++ 类的构造函数

默认构造函数

如果你定义一个类,并且没有给它定义构造函数。编译器会为这个类提供默认的构造函数。如果你提供了构造函数,编译器是不会再为你提供一个默认构造函数的。编译器提供的默认构造函数什么都没做。类的成员变量将遵守默认的初始化规则。

编译器提供的默认构造函数的初始化规则:

在栈和堆中的类对象的内置或复合类型成员变量将为脏数据;

在全局变量区的类对象的内置或复合类型成员变量初始化为0;

类对象成员将调用默认的构造函数来初始化;

#include <iostream>
using namespace std;
class Box
{
  public:
  int length;
  int width;
};
Box box1;
int main()
{
  Box box2;
  Box *pbox3 = new Box;
  cout<<"box1.length == "<<box1.length<<" box1.width == "<<box1.width<<endl;
  cout<<"box2.length == "<<box2.length<<" box2.width == "<<box2.width<<endl;
  cout<<"box3.length == "<<pbox3->length<<" box3.width == "<<pbox3->width<<endl;
  return 0;
}

上面代码的结果为:

box1.length == 0 box1.width == 0
box2.length == 2686792 box2.width == 1987092020
box3.length == 3811912 box3.width == 3801284

带默认实参的构造函数

就像对普通函数一样可以为构造函数的参数指定默认值。

如果你为类定义了一个默认构造函数,又定义了一个所有参数都有默认的值的构造函数。(技术上来说,这是重载了)用默认构造函数构造类对象时将会产生编译错误。因为编译器不知道选择哪个重载函数。

构造函数的初始化列表

除了在构造函数的函数体中用明确的赋值表达式给类成员赋值(从严格的概念上来说这不是初始化),推荐的做法是使用初始化列表。初始化列表以一个冒号开始,紧接着一个一个用逗号分隔的数据成员列表,每个数据成员后跟一个放在圆括号中的初始化式。构造函数的初始化列表只能在实现中指定而不能在定义体中指定。而类的成员函数(构造函数也不例外)的实现既可以在类的定义体中(内联函数),也可以在类的实现中。

成员的初始化次序

每个成员只能在初始化列表中指定一次。而且成员在初始化列表中出现的顺序并不代表成员的实际初始化顺序。成员的初始化顺序是按照它们在类定义中出现的顺序来的。所以成员的初始化最好不要相互依赖,如果你确定它们要相互依赖,你得清楚它们在类定义中的出现顺序。

构造函数的构造的两个阶段

(1)初始化阶段(根据默认的变量初始化规则和初始化列表来执行);(2)构造函数中的函数体执行阶段(这时构造函数体内的赋值语句才会执行)。

为什么推荐使用初始化列表?

1.在许多类中,初始化和赋值严格来讲都是低效率的:数据成员可能已经被直接初始化了,还要对它进行初始化和赋值。
2.比第一点提到的效率更重要的是,某些类型的数据成员必须要初始化。

有些类型的成员必须在初始化列表中进行初始化,比如const对象和引用类型对象。它们只能初始化而不能赋值。在执行构造函数体之前必须完成初始化。在函数体内对它们赋值会引发编译错误。

类类型的成员变量也要特别注意,如果你不对它在初始化列表中的初始化,编译器将会尝试在初始化阶段调用它的默认构造函数给他初始化。如果它没有默认的构造函数,这将会导致运行时错误。另一种情况是你只在构造函数体内对类对象的成员进行了赋值。初始化阶段将会调用该类对象成员的默认构造函数,计算阶段将会调用构造函数体内指定构造函数。意思是该类对象成员调用了两次构造函数,第二次的会覆盖第一次的。

构造函数与隐式类型转换、explicit

C++支持类型自动转换。可以定义如何将其他类的对象隐式转换为我们的类类型,也可以将我们的类类型对象隐式的转换为其他类型。构造函数有个隐含规则:可以用单个实参类调用的构造函数定义了一个从该形参类型到该类类型的一个隐式转换。有时候这不是你想要的,并且会引发错误。例如你定义了下面的类。

class Box
{
  public:
  Box(int x=1,int y=1);
  int length;
  int width;
};
Box::Box(int x,int y):length(x),width(y)
{
}

如果你Box box= 2来初始化一个Box对象。编译器将2隐式转换为一个Box对象,相当于调用了构造函数Box(2)。

如果你在需要Box类型参数的函数调用中传入的是一个int实参,将会构造一个临时的Box对象再传入函数作参数。函数结束后,这Box对象也就消失了,这有什么用呢?这几乎肯定是一个错误。对此我们可以:

1.用关键字explicit阻止构造函数定义的隐式转换

在类构造函数的声明前加上explicit关键字(注意不能在定义中加),可以阻止隐式转换。

class Box
{
  public:
  explicit Box(int x=1,int y=1);
  int length;
  int width;
};

如果你再这样定义一个Box对象:Box box = 2或者将int类型对象作为参数当做Box对象传给某个函数,将会引发编译错误。

2.每次转换,自己显示的使用构造函数。这样可以防止隐式转换。
在需要Box对象实参的的函数调用中用func(Box(2))来调用类的构造函数创建一个临时对象,防止自动的隐式转换。

建议:除非有明确的理由允许隐式转换,可以用单个参数调用的构造函数都应该定义为explicit。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • 详解C++ 拷贝构造函数和赋值运算符

    本文主要介绍了拷贝构造函数和赋值运算符的区别,以及在什么时候调用拷贝构造函数.什么情况下调用赋值运算符.最后,简单的分析了下深拷贝和浅拷贝的问题. 拷贝构造函数和赋值运算符 在默认情况下(用户没有定义,但是也没有显式的删除),编译器会自动的隐式生成一个拷贝构造函数和赋值运算符.但用户可以使用delete来指定不生成拷贝构造函数和赋值运算符,这样的对象就不能通过值传递,也不能进行赋值运算. class Person { public: Person(const Person& p) = dele

  • C++中的构造函数与析造函数详解

    C++中的构造函数与析造函数详解  构造函数的概念 (1)构造函数是特殊的成员函数         (2)当创建类类型的新对象时,系统自动会调用构造函数         (3) 构造函数是为了保证对象的每个数据成员都被正确的初始化.         创建构造函数的注意事项: (1) 函数名与类名相同:         (2) 没有返回类型,返回类型也不能是void型         (3) 构造函数通常情况下声明为public,否则不能像其它成员函数那样被显示的调用         (4) 构造

  • 完全掌握C++编程中构造函数使用的超级学习教程

    构造函数是一种可初始化其类的实例的成员函数.构造函数具有与类相同的名称,没有返回值.构造函数可以具有任意数量的参数,类可以具有任意数量的重载构造函数.构造函数可以具有任何可访问性(公共.受保护或私有).如果未定义任何构造函数,则编译器会生成不采用任何参数的默认构造函数:可以通过将默认构造函数声明为已删除来重写此行为. 构造函数顺序 构造函数按此顺序执行工作: 按声明顺序调用基类和成员构造函数. 如果类派生自虚拟基类,则会将对象的虚拟基指针初始化. 如果类具有或继承了虚函数,则会将对象的虚函数指针

  • 详解C++中对构造函数和赋值运算符的复制和移动操作

    复制构造函数和复制赋值运算符 从 C++ 11 中开始,该语言支持两种类型的分配:复制赋值和移动赋值. 在本文中,"赋值"意味着复制赋值,除非有其他显式声明. 赋值操作和初始化操作都会导致对象被复制. 赋值:在将一个对象的值赋给另一个对象时,第一个对象将复制到第二个对象中. 因此, Point a, b; ... a = b; 导致 b 的值被复制到 a 中. 初始化:在以下情况下将进行初始化:声明新对象.参数通过值传递给函数或值通过值从函数返回. 您可以为类类型的对象定义"

  • 详谈C++何时需要定义赋值/复制构造函数

    继承和动态内存分配 假设基类使用了动态内存分配,而且定义了析构函数.复制构造函数和赋值函数,但是在派生类中没有使用动态内存分配,那么在派生类中不需要显示定义析构函数.复制构造函数和赋值函数. 当基类和派生类采用动态内存分配时,派生类的析构函数.复制构造函数.赋值运算符都必须使用相应的基类方法来处理基类元素.这种要求是通过三种不同的方式来满足的.对于析构函数.这是自动完成的,也就是说在派生类的析构函数中无需显示调用基类的析构函数.对于构造函数,这是通过在初始化成员列表中调用基类的复制构造函数来完成

  • C++详解默认参数的构造函数及简单实例代码

    现在给大家介绍下 有默认参数的构造函数: 大家知道函数获取形参的时候是通过函数调用时在实参里获得的,因此我们必须保证 实参的个数 和 形参的个数必须相同.而且有些情况下我们对于实参或许都是个固定的值.例如 我们需要计算长方形的面积 长x宽 但是用户可以不输入长 而且如果用户指定的情况下默认的长为 3,但是如果用户指定了则使用用户指定的宽 这就是用到默认参数了! 代码: #include <iostream> using namespace std; int area(int l,int w=3

  • C++ 类的构造函数详解及实例

    C++ 类的构造函数 默认构造函数 如果你定义一个类,并且没有给它定义构造函数.编译器会为这个类提供默认的构造函数.如果你提供了构造函数,编译器是不会再为你提供一个默认构造函数的.编译器提供的默认构造函数什么都没做.类的成员变量将遵守默认的初始化规则. 编译器提供的默认构造函数的初始化规则: 在栈和堆中的类对象的内置或复合类型成员变量将为脏数据: 在全局变量区的类对象的内置或复合类型成员变量初始化为0: 类对象成员将调用默认的构造函数来初始化: #include <iostream> usin

  • C++ 静态成员的类内初始化详解及实例代码

    C++ 静态成员的类内初始化详解及实例代码 一般来说,关于C++类静态成员的初始化,并不会让人感到难以理解,但是提到C++ 静态成员的"类内初始化"那就容易迷糊了. 我们来看如下代码: //example.h #include<iostream> #include<vector> using namespace std; class Example{ public: static double rate = 6.5; static const int vecSi

  • C++/java 继承类的多态详解及实例代码

    C++/java 继承类的多态详解 学过C++和Java的人都知道,他们二者由于都可以进行面向对象编程,而面向对象编程的三大特性就是封装.继承.多态,所有今天我们就来简单了解一下C++和Java在多态这方面的不同. 首先我们各看一个案例. C++ //测试继承与多态 class Animal { public: char name[128]; char behavior[128]; void outPut() { cout << "Animal" << endl

  • javascript中的类,继承,构造函数详解

    目录 前言 一.Class类 二.es5构造函数 三.实例.类的关系 实例的原型指向类的原型 Constructor 四.继承 es6继承 es5继承的实现 总结 前言 在es5中实现一个构造函数,并用new调用,即可得到一个类的实例.到了es6发布了Class的写法,js的面向对象变成也变得比较规范了.聊到类就不能不说类的构造函数以及类如何继承 一.Class类 定义一个类: class A { constructor(name){ this.name = name } getName(){

  • Kotlin 的注解类详解及实例

    Kotlin 的注解类详解及实例 注解声明 注解是将元数据附加到代码的方法.要声明注解,请将 annotation 修饰符放在类的前面: annotation class Fancy 注解的附加属性可以通过用元注解标注注解类来指定: @Target 指定可以用 该注解标注的元素的可能的类型(类.函数.属性.表达式等): @Retention 指定该注解是否 存储在编译后的 class 文件中,以及它在运行时能否通过反射可见 (默认都是 true): @Repeatable 允许 在单个元素上多次

  • Java NumberFormat 类的详解及实例

     Java NumberFormat 类的详解及实例 概要: NumberFormat 表示数字的格式化类, 即:可以按照本地的风格习惯进行数字的显示. 此类的定义如下: public abstract class NumberFormat extends Format MessageFormat .DateFormat .NumberFormat 是 Format 三个常用的子类,如果要想进一步完成一个好的国际化程序,则肯定需要同时使用这样三个类完成,根据不同的国家显示贷币的形式. 此类还是在

  • java 中 阻塞队列BlockingQueue详解及实例

    java 中 阻塞队列BlockingQueue详解及实例 BlockingQueue很好的解决了多线程中数据的传输,首先BlockingQueue是一个接口,它大致有四个实现类,这是一个很特殊的队列,如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒.同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作.

  • 玩转JavaScript OOP - 类的实现详解

    概述 当我们在谈论面向对象编程时,我们在谈论什么? 我们首先谈论的是一些概念:对象.类.封装.继承.多态. 对象和类是面向对象的基础,封装.继承和多态是面向对象编程的三大特性. JavaScript提供了对象却缺乏类,它不能像C#一样能显式地定义一个类. 但是JavaScript的函数功能非常灵活,其中之一就是构造函数,结合构造函数和原型对象可以实现"类". 对象和类的概念 对象 "对象"是面向对象编程中非常重要的一个概念,一个对象是一个"东西"

  • C++ 单例模式的详解及实例

    C++ 单例模式的详解及实例 1.什么叫单例模式? 单例模式也称为单件模式.单子模式,可能是使用最广泛的设计模式.其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享.有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘. 通过单例模式, 可以做到: (1)确保一个类只有一个实例被建立 (2)提供了一个对对象的全局访问指针 (3)在不影响单例类的客户端

  • Java反射技术详解及实例解析

    前言 相信很多人都知道反射可以说是Java中最强大的技术了,它可以做的事情太多太多,很多优秀的开源框架都是通过反射完成的,比如最初的很多注解框架,后来因为java反射影响性能,所以被运行时注解APT替代了,java反射有个开源框架jOOR相信很多人都用过,不过我们还是要学习反射的基础语法,这样才能自己写出优秀的框架,当然这里所讲的反射技术,是学习Android插件化技术.Hook技术等必不可少的! 一.基本反射技术   1.1 根据一个字符串得到一个类 getClass方法 String nam

随机推荐