个人知识库 个人知识库
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub

Agnes001

坚持是一件很伟大的事业
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub
  • C语言

    • 内存
    • 数组
    • 指针
    • 字符串
    • 预处理
    • 结构和联合
    • 文件操作
    • 标准函数库
    • C语言总结
  • CPlusPlus

  • Lua技术栈

  • edoyun

  • 内存管理

  • 数据结构

  • 网络编程

  • Linux

  • 池化技术

  • 操作系统

  • python

  • 编程技术
  • CPlusPlus
Agnes001
2022-01-02

C++创建对象时区分圆括号( )和大括号{ }

提示

首先,需要分清楚初始化与赋值,初始化是创建变量时赋予一个初始值;赋值的是把对象的当前值擦除,而以一个新值来替代。

初始值可以借助大括号'{ }',等号 '=' ,圆括号 '( )' :

int x(0);    // 初始值在圆括号内

int y = 0;   // 初始值在等号后面

int z{0};    // 初始值在大括号内

使用拷贝初始化时,只能提供一个初始值。 类内成员的初始化(类内初始值),可以使用花括号和等号,但不能使用圆括号。 初始元素值的列表,只能使用列表初始化。 当用于内置类型的变量时,使用列表初始化且初始值存在丢失信息的风险,则编译器报错。

# 1 列表初始化

# 2 直接初始化

使用()的赋值方式,称之为直接初始化。

# 3 拷贝初始化

如果使用等号(=)初始化一个变量,实际执行的是拷贝初始化,编译器把右侧的初始值拷贝到新创建的对象中去。

以下内容来自:https://zhuanlan.zhihu.com/p/268894227

C++11的对象初始化的语法选择是不堪和混乱的。总的来说,初始值可以借助大括号'{ }',等号 '=' ,圆括号 '( )' :

int x(0); // 初始值在圆括号内

int y = 0; // 初始值在等号后面

int z{0}; // 初始值在大括号内 使用等号初始化经常会让C++初学者认为会进行一次赋值,但不是那样的,对于内置类型,例如int,初始化和赋值操作的差别是模糊的。但是对于用户定义的类,区分初始化和赋值操作是很重要的,因为这会导致不同的函数调用:

Widget w1; // 调用默认构造函数

Widget w2 = w1; // 不是赋值操作,调用拷贝构造函数

w1 = w2; // 赋值操作,调用operator=函数 因为初始化的语法很混乱,而且有些情况无法实现,所以C++11提出了统一初始化语法:一种至少在概念上可以用于表达任何值的语法。它的实现基于大括号,所以我称之为大括号初始化。

使用大括号可以更容易的初始化容器列表初始化:std::vector v{1, 3, 5};

大括号也可以用于类内成员的默认初始值,在C++11中,等号”=”也可以实现,但是圆括号 '( )' 则不可以:

class Widget { ... private: int x{ 0 }; // x的默认初始值为0 int y = 0; // 同上 int z( 0 ); // 报错 } 另一方面,不可拷贝对象(例如,std::atomic)可以用大括号和圆括号初始化,但不能用等号:

std::atomic ai1{ 0 }; // 可以

std::atomic ai2( 0 ); // 可以

std::atomic ai3 = 0; // 报错 注意:当大括号初始化用于内置类型的变量时,如果我们初始值存在丢失信息的风险,则编译器将报错:

doubel ld = 3.14; int a {ld}; // 报错,存在信息丢失风险 int b (ld); // 正确 大括号初始化的另一个值得注意的特性是它会免疫C++中的最让人头痛的歧义。当开发者想要一个默认构造的对象时,程序会不经意地声明个函数而不是构造对象。

Widget w1(10); // 调用Widget的带参构造函数 但当你尝试用类似的语法调用无参构造时,你声明了个函数,而不是创建对象:

Widget w2(); // 最让人头痛的歧义,声明了一个名为w2,不接受任何参数,返回Widget类型的函数! Widget w2; // 正确:w2是个默认初始化的对象 使用大括号包含参数是无法声明为函数的,所以使用大括号默认构造对象不会出现这个问题:

Widget w2{}; // 无歧义 我们讲了很多大括号初始化的内容,这种语法可以用于多种场景,还可以避免隐式范围窄化转换,又免疫C++的最让人头痛的歧义问题。一举多得,那么为什么这条款不起名为“用大括号初始化语法替代其他”呢?

大括号初始化的缺点是它有时会显现令人惊讶的的行为。这些行为的出现是因为与std::initializer_list混淆了。在构造函数中,只要形参不带有std::initializer_list,圆括号和大括号行为一致:

class Widget { public: Widget(int i, bool b); Widget(int i, double d); ... };

Widget w1(10, true); // 调用第一个构造函数

Widget w2{10, true}; // 调用第一个构造函数

Widget w3(10, 5.0); // 调用第二个构造函数

Widget w4{10, 5.0}; // 调用第二个构造函数 但是,如果构造函数的形参带有std::initializer_list,调用构造函数时大括号初始化语法会强制使用带std::initializer_list参数的重载构造函数:

class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list il); ... }; Widget w1(10, true); // 使用圆括号,调用第一个构造函数

Widget w2{10, true}; // 使用大括号,强制调用第三个构造函数,10和true被转换为long double

Widget w3(10, 5.0); // 使用圆括号,调用第二个构造函数

Widget w4{10, 5.0}; // 使用大括号,强制调用第三个构造函数,10和5.0被转换为long double 就算是正常的拷贝构造和赋值构造也可以被带有std::initializer_list的构造函数劫持:

class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list il); operator float() const; // 支持隐式转换为float类型 ... };

Widget w5(w4); // 使用圆括号,调用拷贝构造函数

Widget w6{w4}; // 使用大括号,调用第三个构造函数 // 原因是先把w4转换为float,再把float转换为long dobule

Widget w7(std::move(m4)); // 使用圆括号,调用移动构造函数

Widget w8{std::move(m4)}; // 使用大括号,调用第三个构造函数,理由同w6 编译器用带有std::initializer_list构造函数匹配大括号初始值的决心是如此的坚定,即使带有std::initializer_list的构造函数是无法调用的,编译器也会忽略另外两个构造函数(第二个还是参数精确匹配的):

class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_list il); // long double 改为 bool ... };

Widget w{10, 5.0}; // 报错,因为发生范围窄化转换 // 编译器会忽略另外两个构造函数(第二个还是参数精确匹配的!) 只有当大括号内的值无法转换为std::initializer_list元素的类型时,编译器才会使用正常的重载选择方法:

class Widget { public: Widget(int i, bool b); Widget(int i, double d); Widget(std::initializer_liststd::string il); // bool 改为 std::string ... };

Widget w1(10, true); // 使用圆括号,调用第一个构造函数

Widget w2{10, true}; // 使用大括号,不过调用第一个构造函数,因为无法转换为string

Widget w3(10, 5.0); // 使用圆括号,调用第二个构造函数

Widget w4{10, 5.0}; // 使用大括号, 不过调用第二个构造函数,因为无法转换为string 不过这里有一个有趣的边缘情况。一个大括号内无参的构造函数,不仅可以表示默认构造,还可以表示带std::initializer_list的构造函数。你的空括号是表示哪一种情况呢?

正确答案是你将使用默认构造,一个空的大括号表示的是没有参数,而不是一个空的std::initializer_list:

class Widget { public: Widget(); Widget(std::initializer_list il); ... };

Widget w1; // 调用默认构造函数

Widget w2{}; // 调用默认构造函数 如果你想要用一个空的std::initializer_list参数来调用带std::initializer_list构造函数,那么你需要把大括号作为参数,即把空的大括号放在圆括号内或者大括号内:

Widget w4({}); // 用了一个空的list来调用带std::initializer_list构造函数 此时此刻,大括号初始化,std::initializer_list,构造函数重载之间的复杂关系在你的大脑中冒泡,你可能想要知道这些信息会在多大程度上关系到你的日常编程。可能比你想象中要多,因为std::vector就是一个被它们直接影响的类。std::vector中有一个可以指定容器的大小和容器内元素的初始值的不带std::initializer_list构造函数,但它也有一个可以指定容器中元素值的带std::initializer_list函数。

std::vector v1(10, 20); // 使用不带std::initializer_list的构造函数 // 创建10个元素的vector,每个元素的初始值为20

std::vector v2{10, 20}; // 使用带std::initializer_list的构造函数 // 创建2个元素的vector,元素值为10和20

编辑此页
#initialize
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式