一文带你分清C++的定义,声明和初始化

目录
  • 定义
  • 初始化
  • 声明
  • 实例
    • 声明时提供初值
    • 在构造函数内赋初值,而不用列表
  • 总结

定义

变量的定义用于为变量分配存储空间,还可以为变量指定初始值。

int units_sold;
double sales_price, avg_price;
std::string title;
Sales_item curr_book; // class Sales_item

初始化

C++ 支持两种初始化变量的形式:复制初始化和直接初始化。复制初始化语法用等号(=),直接初始化则是把初始化式放在括号中。

int ival(1024); // direct-initialization
int ival = 1024; // copy-initialization

初始化不是赋值。初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。

当定义没有初始化式的变量时,系统有时候会帮我们初始化变量。

1.内置类型变量

(Built-in Types,即int,float,double,void,char,bool等。注意string是标准库定义的类型,不是内置类型)

在函数体外定义的变量都初始化成 0,在函数体里定义的内置类型变量不进行自动初始化。

2.类

类通过定义一个或多个构造函数来控制类对象的初始化。创建类类型的新对象,都要执行构造函数,保证每个对象的数据成员具有合适的初始值。

构造函数可以包含一个构造函数初始化列表,以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。与任意的成员函数一样,构造函数可以定义在类的内部或外部。

构造函数初始化只在构造函数的定义中而不是声明中指定。

//将 isbn 成员初始化为 book 形参的值,将 units_sold 和 revenue 初始化为 0。
Sales_item::Sales_item(const string &book):
          isbn(book), units_sold(0), revenue(0.0) { }

如果没有提供初始化式,那么就会使用默认构造函数。如果类具有默认构造函数,那么就可以在定义该类的变量时不用显式地初始化变量。例如,string 类定义了默认构造函数来初始化 string 变量为空字符串。

string a;
cout << "a: " << a <<endl;

输出:

a:

此外,省略初始化列表在构造函数的函数体内对数据成员赋值是合法的。

Sales_item::Sales_item(const string &book)
     {
         isbn = book;
         units_sold = 0;
         revenue = 0.0;
     }

不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。(也就是函数体执行以前)→ 这里似乎有些难以理解,通过后文的实例也许你能明白

在构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。运行该类型的默认构造函数,来初始化类类型的数据成员。内置或复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为 0。

未初始化的变量

局部作用域的内置类型变量将不被自动初始化,这可能导致其成为未初始化的变量。这是常见的程序错误,但编译器无法检测出所有未初始化变量的使用。→ 你肯定可以理解这可能导致的灾难性后果了(这竟然不可以运行,为什么呢?这竟然可以运行,为什么呢?.jpg)再稍微解释一下原因:问题出在未初始化的变量事实上都有一个值。编译器把该变量放到内存中的某个位置,而把这个位置的无论哪种位模式都当成是变量初始的状态。当被解释成整型值时,任何位模式都是合法的值——虽然这个值不可能是程序员想要的。因为这个值合法,所以使用它也不可能会导致程序崩溃。可能的结果是导致程序错误执行和/或错误计算。

声明

声明用于向程序表明变量的类型和名字。定义也是声明:当定义变量时我们声明了它的类型和名字。

可以通过使用extern关键字声明变量名而不定义它。extern 声明不是定义,也不分配存储空间。事实上,它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。

extern int i; // declares but does not define i
int i; // declares and defines i

只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为 extern:

extern int i = 10; //defines i

在 C++ 语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量。

实例

#include <iostream>
#include <string>
using namespace std;

//类x的声明
//如果这一部分放在main()函数后面,报错:error: 'x' was not declared in this scope
//在实际工程中,这部分声明将放在头文件(.h)中,而构造函数及成员函数的定义则放在.cpp文件中
class x{
public:
    x(int a, int b, string c);
    void print_data();
private:
    //类数据成员的变量名最好在开头加一个字母m(即member)
    int ma;
    int mb;
    string mc;

};

int main(){
    int a1(2); //直接初始化
    int b1 = 3; //复制初始化
    string c1; //默认构造函数初始化string变量为空字符串
    c1 = "dwkw"; //赋值
    x data(a1, b1, c1); //调用构造函数初始化
    return 0;
    print_data();
}

//构造函数定义
x::x(int a, int b, string c):ma(a), mb(b), mc(c){}
//成员函数定义
void x::print_data(){
    cout << "ma: " << ma << endl;
    cout << "mb: " << mb << endl;
    cout << "mc: " << mc << endl;
}

输出:

ma: 2
mb: 3
mc: dwkw

声明时提供初值

如果在类的声明中就对数据成员提供初值,而不在初始化列表中提供,程序可以执行,输出ma的值为1。

class x{
public:
    x(int a, int b, string c);
    void print_data();
private:
    int ma = 1; //声明时提供初值
    int mb;
    string mc;
};
//去掉初始化列表
x::x(int a, int b, string c):mb(b), mc(c){}

这一做法在早期版本不予支持,但从c++11就可以了。[2]

不过这破坏了类的抽象性,并不建议这样做。

查看c++版本的方法:[3]

cout << __cplusplus << endl; //输出c++版本

在构造函数内赋初值,而不用列表

前面提到省略初始化列表在构造函数的函数体内对数据成员赋值是合法的。

//去掉初始化列表,在构造函数体内赋值
//其它代码保持不变
x::x(int a, int b, string c){
    cout << "赋值前: " << endl;
    print_data();
    cout << "赋值后: " << endl;
    ma = 4;
    mb = 5;
    mc = "ser";
}

输出:

赋值前: 
ma: 4199744
mb: 0
mc: 
赋值后: 
ma: 4
mb: 5
mc: ser

实际上我就没写初始化列表,但系统它就会在这里执行初始化。总之就会在执行构造函数体内的语句之前初始化(如果它可以自动初始化),即使根本没写初始化列表。→ 啧,我就像在说绕口令,希望你能明白我的意思

但是ma和mb都是局部作用域(我不确定类作用域是否是局部作用域,但从输出来看,ma不是0,所以应该没有能够初始化)的内置类型变量,不进行自动初始化;mc有默认构造函数,自动初始化为空字符串。

而后,执行构造函数体内部的语句,将对ma和mb进行初始化(我想这里应该是初始化而不是赋值),对mc赋值。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 详解C++ 前置声明

    前置声明是C/C++开发中比较常用的技巧,主要用在三种情形: 变量/常量,例如extern int var1;; 函数,例如void foo();,注意类的成员函数无法单独做前置声明: 类,例如class Foo;,也可以前置声明模板类:template class<typename T1, int SIZE>Foo;.如果类包含在名字空间中,需在名字空间内做前置声明:namespace tlanyan {class Foo;};,而不能这样:class tlanyan::Foo;. 前置声明

  • C++数组的定义详情

    目录 1.数组概念 2.数组的复杂声明 3.数组到指针 4.数组操作 4.1获取数组元素个数 4.2使用for循环遍历数组(C++11开始支持) 5.拓展 5.1C字符串 5.2vector 5.3string 6.思考 6.1思考以下代码输出什么? 6.2以下代码能够编译通过吗? 6.3在另一个文件中定义了数组,如何在该文件中定义? 上一篇讲解了类型,通过类型来开始本篇的学习: int a[10]; 上述代码中的a是什么类型呢? 相信很多人都知道是一个数组类型,具体来说是一个int[10]的类

  • C++成员初始化列表

    文章转自: 公众号:Coder梁(ID:Coder_LT) 除了可以使用构造函数Classy::Classy(int n, int m): mem1(n), mem2(0), mem3(n*m+2) {    ...};类成员进行初始化之外,C++还提供了另外一种初始化的方法,叫做成员初始化列表. 我们假设Classy是一个类,而mem1,mem2和mem3都是这个类的数据成员,那么类构造函数可以写成: Classy::Classy(int n, int m): mem1(n), mem2(0)

  • C++类成员初始化的三种方式

    目录 一.初始化方式 1.初始化方式一:初始化列表 2.初始化方式二:构造函数初始化 3.初始化方式三:声明时初始化(也称就地初始化,c++11后支持) 二.声明时初始化->初始化列表->构造函数初始化 1.声明时初始化的使用场景 2.列表初始化的使用场景 3.构造函数初始化的使用场景 前言: 在C++98中,支持了在类声明中使用等号"="加初始值的方式,来初始化类中静态成员常量.这种声明方式我们也称之为"就地"声明.就地声明在代码编写时非常便利,不过C

  • C++类的定义与实现

    目录 一.类的定义 二.类的实现 1.成员函数 2.内联函数 文章转自 微信公众号:Coder梁(ID:Coder_LT) 一.类的定义 根据C++ Primer中的描述,类的定义是一种将抽象转换为用户定义类型的C++工具.也就是说类的实质是一种用户自定义类型,它可以将数目表示和操作数据的方法组合成一个整洁的包. 在实际开发当中,想要实现一个类,并编写一个使用它的程序是相对比较复杂的,涉及多个步骤. 通常,我们会将类的定义放在头文件当中,并将实现的代码放在源代码文件中.我们来看C++ Prime

  • 区分c++中的声明与定义

    C++编码过程中,我们经常谈及"定义"和"声明",二者是编程过程中的基本概念.我们需要使用一个变量.类型(类.结构体.枚举.共用体)或者函数时,我们需要提前定义和声明.定义和声明的过程,就像我们向图书馆借阅书籍一般,需要先完成书籍的印刷,即创造出书籍,这是一个定义的过程,有了书籍,我们需要到图书馆完成借阅的登记手续,这是申明的过程.完成了申明,我们有了使用书籍的权限,就可以尽情的畅游在知识的海洋.如果说书籍是自己委托印刷厂印刷的,那么你无需向他人借阅,即无需声明,可

  • 一文带你分清C++的定义,声明和初始化

    目录 定义 初始化 声明 实例 声明时提供初值 在构造函数内赋初值,而不用列表 总结 定义 变量的定义用于为变量分配存储空间,还可以为变量指定初始值. int units_sold; double sales_price, avg_price; std::string title; Sales_item curr_book; // class Sales_item 初始化 C++ 支持两种初始化变量的形式:复制初始化和直接初始化.复制初始化语法用等号(=),直接初始化则是把初始化式放在括号中.

  • 一文带你搞懂C语言预处理宏定义

    目录 预定义符号 #define #define 定义标识符 #define 定义宏 替换规则 # 和## 预定义符号 这些预定义符号都是语言内置的 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 VS环境下未定义__STDC__ ,说明Visual Studio并未完全遵循ANSI C. #define #defi

  • 一文带你了解 C# DLR 的世界(DLR 探秘)

    在很久之前,我写了一片文章详解C# 匿名对象(匿名类型).var.动态类型 dynamic,可以借鉴.因为那时候是心中想当然的认为只有反射能够在运行时解析对象的成员信息并调用成员方法.后来也是因为其他的事一直都没有回过头来把这一节知识给补上,正所谓亡羊补牢,让我们现在来大致了解一下DLR吧. DLR 全称是 Dynamic Language Runtime(动态语言运行时).这很容易让我们想到同在C#中还有一个叫 CLR 的东西,它叫 Common Language Runtime.那这两者有什

  • 一文带你入门JDK8新特性——Lambda表达式

    Lambda简介 Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构. JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便.高效. 对接口的要求 虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现.Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法 jd

  • 一文带你彻底搞懂Lambda表达式

    1. 为什么使用Lambda表达式 Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升. 我们来看一下使用lambda之前创建匿名内部类: new Thread(new Runnable() { @Override public void run() { System.out.println("执行Runnable方法"); } });

  • 一文带你走进js-数据类型与数据结构的世界

    目录 2. 数据类型 2.1 原始类型(6 种原始类型,使用 typeof 运算符检查) 2.2 null 与 Object 3. 原始值 3.1 原始值基本概念 3.2 各类型说明 1. 什么叫动态类型 JavaScript 是一种弱类型或者说动态语言. 我们不用提前声明变量的类型,在程序运行过程中,类型会被自动确定. 这也意味着你可以使用同一个变量保存不同类型的数据 let a; // 初始不给任何类型 a = 11; // a此时是number类型 a = "二大爷"; // a

  • 一文带你了解CNN(卷积神经网络)

    目录 前言 一.CNN解决了什么问题? 二.CNN网络的结构 2.1 卷积层 - 提取特征 卷积运算 权重共享 稀疏连接 总结:标准的卷积操作 卷积的意义 1x1卷积的重大意义 2.2 激活函数 2.3 池化层(下采样) - 数据降维,避免过拟合 2.4 全连接层 - 分类,输出结果 三.Pytorch实现LeNet网络 3.1 模型定义 3.2 模型训练(使用GPU训练) 3.3 训练和评估模型 前言   在学计算机视觉的这段时间里整理了不少的笔记,想着就把这些笔记再重新整理出来,然后写成Bl

  • 一文带你了解vue3.0响应式

    目录 使用案例 reactive API相关的流程 reactive createReactiveObject 创建响应式对象 mutableHandlers 处理函数 get函数 get函数的的调用时机 track 收集依赖 set函数 trigger 分发依赖 get和副作用渲染函数关联 副作用渲染函数的执行过滤 结尾 我们知道Vue 2.0是利用Ojbect.defineProperty对对象的已有属性值的读取和修改进行劫持,但是这个API不能监听对象属性的新增和删除,此外为了深度劫持对象

  • 一文带你理解 Vue 中的生命周期

    目录 1.beforeCreate & created 2.beforeMount & mounted 3.beforeUpdate & updated 4.beforeDestroy & destroyed 5.activated & deactivated 前言: 每个 Vue 实例在被创建之前都要经过一系列的初始化过程.例如需要设置数据监听.编译模板.挂载实例到 DOM.在数据变化时更新 DOM 等.同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户

  • 一文带你掌握Java8中Lambda表达式 函数式接口及方法构造器数组的引用

    目录 函数式接口概述 函数式接口示例 1.Runnable接口 2.自定义函数式接口 3.作为参数传递 Lambda 表达式 内置函数式接口 Lambda简述 Lambda语法 方法引用 构造器引用 数组引用 函数式接口概述 只包含一个抽象方法的接口,称为函数式接口. 可以通过 Lambda 表达式来创建该接口的对象. 可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口.同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口.

随机推荐