C++程序设计:原理与实践读书笔记(第八章)

        如果一个声明(还)给出了声明的实体的完整描述的话,我们称之为定义。下面是一个定义的例子:

int a=7;
vector<double> v;
double sqrt(double d){/*...*/}

        对比定义,我们按惯例用“声明”表示“不是定义的声明”。如:

int x = 7;           //定义
extern int x;        //声明
extern int x;        //另一个声明

double sqrt(double);                //声明
double sqrt(double d){/*...*/}      //定义
double sqrt(double);                //sqrt的另一个声明
double sqrt(double);                //sqrt的再一个声明

int sqrt(double);                   //错误:sqrt不一致的声明

        声明的类别:C++允许程序员定义很多类别的实体,我们比较关心的有:变量、常量、函数、名字空间、类型、模板。

        变量和常量的声明,指定一个名字和一个类型,并可进行初始化。例如:

int a;         //不带初始化
double d=7;    //使用=语法初始化
vector<int> vi(10);            //使用()语法进行初始化
vector<int> vi2 {1,2,3,4};     //使用{}语法进行初始化

        常量的声明语法与变量一样,差别在于类型之前多了一个关键字const,而且必须初始化。

const int x=7;        //使用=语法进行初始化
const int x2{9};      //使用{}语法进行初始化
const int y;          //错误:未进行初始化

        我们倾向于使用{}初始化语法。

        我们通常不对string、vector等对象进行初始化。因为这些类型定义了默认初始化机制。vector对象的默认初始化值为空(不包含任何元素)。string对象的值为空串“”。

        注意:最常使用的变量-局部变量和类成员,是不会被初始化的,除非对其进行初始化,在实践中一定要注意。

        在C++中,对于“别处”定义的功能的声明,管理它们的关键是“头部”。本质上一个头部(header)是一些声明的集合,一般定义于一个文件,因此也称为“头文件”(header file)。这样的头文件随后用#include包含在我们的源文件中。

        C++支持多种类型的作用域:

  • 全局作用域:在任何其他作用域之外的程序区域。
  • 名字空间作用域:一个名字空间作用域嵌套于全局作用域或另一个名字空间作用域中。
  • 类作用域:一个类内的程序区域。
  • 局部作用域:位于{...}大括号之间或函数参数列表中的程序区域。
  • 语句作用域:例如for语句内的程序区域。

        当已有的函数中有参数不再使用,但是已经有大量“外部”代码调用时使用该参数。当我们不希望重写这些代码时(不想更改函数声明)。我们可以不为其命名:

int my_find(vector<string> vs, string s, int)
{
    for(int i=0;i<vs.size();++i)
        if(vs[i] == s) return i;
    return -1;
}

        函数参数的传递方式包括:传值、传常量引用、传引用。我们的基本原则是:

  1. 使用传值方式传递非常小的对象。
  2. 使用传常量引用方式传递你不需要修改的大对象。
  3. 让函数返回一个值,而不是修改通过引用参数传递来的对象。
  4. 只有迫不得已才使用传引用方式。

       在编译时对函数进行计算可以通过将函数声明为constexpr来实现。一个constexpr函数若给定常量表达式作为参数, 则函数计算将在编译时完成。

constexpr double xscale = 10;    //缩放因子
constexpr double yscale = 0.8;

constexpr Point scale(Point p){return {xscale * p.x, yscale * p.y};};

        C++11中,要求constexpr函数体只能包含一条return语句;在C++14中,还可以写简单的循环语句。一个constexpr函数不允许有副作用,即它不能改变函数体之外的变量值。以下是违反函数简单性规则的一个函数例子:

int gob = 9;

constexpr void bad(int& arg)    //错误:没有返回值
{
    ++arg;                      //错误:通过参数修改了某些变量的值
    gob = 7;                    //错误:修改了非局部变量
}

        除非是在一些非常特殊的情况下,否则一般来说使用全局变量不是一个好主意。程序员没有有效方法获知程序的哪个部分读或写了一个全局变量。另一个问题是,在不同的编译单元中的全局变量的初始化顺序是不确定的。

//file1.cpp
int x1 = 1;
int y1 = x1 + 2;

//file2.cpp
extern int y1;
int y2 = y1 + 2;    //y2为2或者5

        如果确实需要使用一个全局变量(或常量),而且需要对它进行复杂的初始化时,一种常用的技术是编写一个函数,返回我们需要的初值。例如:

const Date default_date()
{
    return Date(1970,1,1);
}

        如果需要频繁调用的话,可以这样做:

const Date& default_date()
{
    static const Date dd(1970,1,1);    //第一次到达这里时初始化dd
    return dd;
}

        名字空间(namespace),即是无须定义一个类型就能将类、函数、数据和类型组织成一个可识别的命名实体。例如:

namespace Graph_lib{
    struct Color {/*...*/};
    struct Shape {/*...*/};
    struct Line:Shape {/*...*/};
    struct Function:Shape {/*...*/};
    struct Text:Shape {/*...*/};
    //...
    int gui_main() {/*...*/};
}

        由一个名字空间的名字(或一个类名)和一个成员名组合而成的名字称为全限定名。

        一个一般性的原则是,除非是std这种在某个应用领域中大家已经熟悉的名字空间,否则最好不要使用using指令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值