Effective C++笔记之一:声明、定义、初始化与赋值

一.声明(Declaration)
区分声明和定义可以让C++支持分开编译,声明常常见于头文件中。源文件包含头文件之后,就可以使用这个变量,即使没有看到该变量的定义。 声明的语法如下:
extern int i; // object declaration
int numDigits(int number); // function declaration
class Widget; // class declaration
 
template<typename T> // template declaration
class GraphNode;
 
extern double pi = 3.1416; // definition
二.定义(Definition)
       定义是为变量分配存储空间,并可能进行初始化。变量一般不能定义在头文件中,除了const变量(local to a file)。
       除了变量,类和函数也有定义的说法,总结如下:
1.对于类来说,一般定义在头文件中。因为编译器需要在每个源文件都看到类的定义才能编译成功;
2.对于一般函数来说,函数声明在头文件中,函数定义则在源文件中;
3.对于inline和constexpr function,编译器需要在每个源文件都看到定义,因此通常也定义在头文件中。

int x; // declaration or definition 
//上面单独的一行,是声明还是定义,判断的原则是看是否占用内存(能否进行初始化)。例如:
class MyClass // 类定义
{
    int x; // 它是声明,因为C++11之前是不允许在类的定义内部直接初始化数据成员的 
    float y = 10.0f; // C++11及以后支持这种写法,但它仍然是个声明
    static char c;   // 这也是个声明,因为如果写成这样 static char c = 'A';
    // 编译器会报错,你需要在类外进行定义并初始化,因为类里面的只是声明而已
};
//但是如果int x;出现在函数定义内部,它就是一个定义了。

这里有一个令人疑惑的地方,头文件的的类MyClass既然是定义,按照“定义”的解释,它应该占有内存,那为何类中包含的内容反而是声明。
       因为类是属于用户自定义的数据类型,与内置类型,比如说int,在使用上类似。类定义只是定义了一种类型,也即说明了一个类,并没有实际定义类的对象,定义的是类,定义类描述的是新的类型,而描述新类型并不会开辟内存空间去存储这样一种新类的对象。
三.初始化(Initialization)
       初始化是指变量在创建的同时获得的初始值。

       当我们定义一个变量时,不提供初始值,那么这个变量就是默认初始化(default initialized)的。默认值由变量的类型和变量的定义位置来决定。
对于built-in type,默认值由变量的定义位置决定。在函数外部定义的全局变量(global variable),函数内部定义的局部静态变量(local static object)全部初始化为0。函数内部定义的局部变量是未初始化的;
       对于class type,由类里的默认构造函数初始化。如果类定义里没有默认构造函数(显示或隐示),则编译出错。

#include <iostream>
using namespace std;
 
int a;
int main()
{
   static int b;
   int c;
 
   cout << a << endl;
   cout << b << endl;
   cout << c<< endl;
   system("pause");
   return 0;
}

在VS执行这段代码,输出变量a的值0,b的值为0,同时VS会报错:Run-Time Check Failure #3 — The variable 'c' is being used without being initialized。 变量a和b被默认初始化为0,变量c未被初始化。

list initialization

       C++11中提供了一种新的初始化方式,list initialization,以大括号包围。
// built-in type initialization
double d1{2.3};    //ok: direct-list-initialization 
double d2 = {2.3}; //ok: copy-list-initialization
// class type initialization
complex<double> z2{d1,d2};
complex<double> z3 = {1,2};  //ok: the = is optional with {...}
vector<int> vec{1,2,3,4,5,6};//ok: a vector of ints
 
long double pi = 3.1415;
int a{pi}, b = {pi};         //error: narrowing conversion required
int c(pi), d = pi;         //ok: implict conversion.
value initialization
       value initialization里,built-in type变量被初始化为0,class type的对象被默认构造(一定要有)初始化。这种方式通常见于STL里的vector和数组,且经常与list initialization结合起来使用,为我们初始化全0数组提供了很大的便利。简单用法如下:
vector<int> ivec(10);            //ten elements, each initialized to 0
vector<string> svec(10);        //ten elmenets, each an empty string
vector<string> v1 = {"a", "an", "the"}; //list initialized
int a[10] = {};                //ten elements, each initialized to 0
int a2[] = {1,2,3};            //list initialized
int a3[5] = {1,2,3};            //equivalent to a3[] = {1,2,3,0,0}
关于类的初始化比较复杂,整理几点:
1.编译器首先编译类成员的声明,包括函数和变量
2.整个类可见后,才编译函数体(所以不管定义顺序,函数里可以用类里的任何变量和函数)
3.C++11提供了in-class initializers机制,C++ Primer里面讲如果编译器支持,推荐使用in-class initializers机制。注意这种机制只支持=,{}形式,不支持()。Constructor Initializer List对变量进行初始化后,才进入构造函数。Constructor Initializer List里忽略的成员变量(为空则相当于全部忽略),会由in-class initializers初始化,或者采取default initialization,然后进入构造函数体,构造函数体实际是给成员二次赋值
4.对于class type成员,会调用其默认构造函数进行default initialization。
5.对于built-in type成员,要么in-class initialization,要么Constructor initializer list。是否会被default initialization与类定义的位置有关,这点和“default initialization”小节中说的built-int type类似

6.类的静态函数成员可以在类内部或者外部定义,而静态数据成员(const除外)则只能在外部定义以及初始化

#include <iostream>
using namespace std;

class testA
{
public:
    testA()
    {
        cout << "A-x:" << x << endl;
        cout << "A-y:" << y << endl;
    }
private:
    int x;
    int y = 10; // in-class initializer
};

class testB
{
public:
    void printf() const
    {
        cout << "B:" << data << endl;
    }
private:
    int data;
    testA a;
};

testB b1;
//A-x:0
//A-y:10
int main()
{
    b1.printf(); //B:0
    testB b2;
//    A-x:32767
//    A-y:10
    b2.printf();//B:-6592

    system("pause");
    return 0;
}

如果是动态初始化的对象:

testB *b1=new testB();
//A-x:0
//A-y:10
int main()
{
    b1->printf();//B:0
    testB *b2 = new testB;
//    A-x:0
//    A-y:10
    b2->printf(); //B:0

    system("pause");
    return 0;
}

还需注意的是数组的初始化。

 定义数组时,如果没有显示提供初始化列表,则数组元素的默认化初始规则同普通变量一样:函数体外定义的内置类型数组,其元素初始为0;函数体内定义的内置类型数组,其元素无初始化;类类型数组无论在哪里定义,皆调用默认构造函数进行初始化,无默认构造函数则必须提供显示初始化列表。
       如果定义数组时,仅提供了部分元素的初始列表,其剩下的数组元素,若是类类型则调用默认构造函数进行初始,若是内置类型则初始为0(不论数组定义位置)。
       对于动态分配的数组,如果数组元素是内置类型,其元素无初始化;如果数组元素是类类型,依然调用默认构造函数进行初始化。也可以在使用跟在数组长度后面的一对空圆括号对数组元素做值初始化。
例如: int *ptrA = new int[10];
    int *ptrB = new int[10] ();
       其中ptrA指向的动态数组其元素未初始化,而ptrB指向的动态数组元素被初始化为0。

四.赋值(Assignment) 
       赋值的结果是左边的操作元,为左值,也就是说,下面的写法语法正确
int a = 0;
(a = 0) = 1; // the final value of a is 1
++i和i++的区别:前者将操作元增加,并且返回改变后的操作元;后者将操作数增加,返回原先值得拷贝作为结果。前置自增返回的结果是左值,后者自增返回的是右值。前置自增操作符做的无用功少,虽然C++编译器对int和指针类型的后置自增操作符作了优化,C++ Primer推荐如无特殊需求,优先使用前置自增操作符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值