c++基础知识(7)

c++ primer plus第九章知识,包括文件的单独编译、存储持续性、作用域、链接性、名称空间。


单独编译

通常,可以将一个程序分为三部分

  1. 头文件:包含结构声明和使用这些结构的函数的原型(.h)
    • 函数原型
    • 使用#define或const定义的符号常量
    • 结构声明
    • 类声明
    • 模板声明
    • 内联函数
  2. 源代码文件:包含与结构相关的函数的代码(.cpp)
  3. 源代码文件:包含调用与结构相关的函数的代码(.cpp)

假设有一个头文件coordin.h,#include时有两种形式:

使用<coordin.h>时,c++编译器将在存储标准头文件的主机系统的文件系统中查找;

使用”coordin.h”时,则编译器 将首先查找当前的工作目录或源代码目录,如果在那里没有找到头文件,则将在标准位置查找。

因此在包含自己的头文件时,应该使用引号而不是尖括号

头文件管理

在同一个文件中只能将一个头文件包含一次,但是在编写代码时很有可能会将头文件包含多次。可以使用预编译器指令#ifndef(if not define)

1
2
3
4
5
6
7
//头文件 "coordin.h"
#ifndef COORFIN_H_
#define COORFIN_H_
···
...
#endif

例子:假设有一个cpp文件,其作用是将直角坐标转换为极坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<iostream>
#include<cmath>

struct polar
{
double distance;
double angle;
}
struct rect
{
double x;
double y;
}

polar rect_to_polar(rect xypos);
void show_polar(polar dapos);

int main()
{
using namespace std;
rect rplace;
polar pplace;

cout << "Enter the x and y: ";
while(cin >> rplace.x >> rplace.y)
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Done.\n";
return 0;
}

polar rect_to_polar(rect xypos)
{
using namespace std;
polar answer;

answer.distance = sqrt( xypos.x *xypos.x + xypos.y * xypos.y );
answer.angle = atan2(xypos.x, xypos.y);
return answer;
}

void show_polar(polar dapos)
{
using namespace std;
const double Rad_to_deg = 57.29577951;

cout << "distance = " << dapos.distance;
cout << ", angle = " << dapos.angle * Rad_to_deg;
cout << " degree\n";
}

可以将以上代码拆分为”coordin.h”、file1.cpp 、file2.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//"coordin.h"
#ifndef COORFIN_H_
#define COORFIN_H_

struct polar
{
double distance;
double angle;
}
struct rect
{
double x;
double y;
}

polar rect_to_polar(rect xypos);
void show_polar(polar dapos);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// file1.cpp

#include<iostream>
#include"coordin.h"

int main()
{
using namespace std;
rect rplace;
polar pplace;

cout << "Enter the x and y: ";
while(cin >> rplace.x >> rplace.y)
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Done.\n";
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// file2.cpp
#include<iostream>
#include<cmath>
#include"coordin.h"

polar rect_to_polar(rect xypos)
{
using namespace std;
polar answer;

answer.distance = sqrt( xypos.x *xypos.x + xypos.y * xypos.y );
answer.angle = atan2(xypos.x, xypos.y);
return answer;
}

void show_polar(polar dapos)
{
using namespace std;
const double Rad_to_deg = 57.29577951;

cout << "distance = " << dapos.distance;
cout << ", angle = " << dapos.angle * Rad_to_deg;
cout << " degree\n";
}

编译链接生成一个可执行程序

1
g++ file1.cpp file2.cpp

存储持续性、作用域和链接性

c++使用三种(在c++11中是四周)不同的方案来存储数据,这些方案的区别在于数据保留在内存中的时间。

  • 自动存储持续性:函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。c++有两种存储持续性为自动的变量。
  • 静态存储持续性: 在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。c++有3种存储持续性为静态的变量。
  • 线程存储持续性(c++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。
  • 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储或堆。

作用域和链接

作用域描述了名称在文件的多大范围内可见。

例如,函数中定义的变量可在该函数中使用,但不能在其他函数中使用

链接性描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中中的函数共享。自动变量的名称没有链接性,因为它们不能共享。

自动存储持续性

在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。

自动变量是存储在栈中的

静态持续变量

c++为静态存储持续性变量提供了3种链接性:

  • 外部链接性(可在其他文件中访问)
  • 内部链接性(只能在当前文件中访问)
  • 无链接性(只能在当前函数或代码块中访问)

这3种链接性都在整个程序执行期间存在。

存储描述 持续性 作用域 链接性 如何声明
静态,无链接性 静态 代码块 在代码块中,使用关键字static
静态,外部链接性 静态 文件 外部 不在任何函数中
静态,内部链接性 静态 文件 内部 不在任何函数中,使用关键字static

静态持续性、外部链接性

链接性为外部的变量通常称为外部变量,它们的存储持续性为静态,作用域为整个文件。外部变量是在函数外部定义的,因此对所有函数而言都是外部的。外部变量也称全局变量。

单定义规则

一方面,在每一个使用外部变量的文件中,都必须声明它;另一方面,c++有“单定义规则”,该规则指出,变量只能有一次定义。

因此,c++提供了两种变量声明:

  • 一种是定义声明,它给变量分配存储空间
  • 另一种是引用声明,它不给变量分配空间,因为它引用已有的变量

引用声明使用关键字extern,且不进行初始化;否则,声明变为定义,导致分配存储空间:

1
2
3
double up;//定义 up=0
extern int blem;//引用
extern char gr = 'z'//定义

如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它:

1
2
3
4
// file1.cpp
extern int cats = 20;
int dogs = 22;
int fleas
1
2
3
4
// file2.cpp
// use cats and dogs from file1.cpp
extern int cats;
extern int dogs;

如果在函数中声明了一个与外部变量同名的变量,这种声明将被视为一个自动变量的定义,当程序执行自动变量所属的函数时,该函数位于作用域内。如果想在该函数内使用同名的外部变量,可以使用作用域解析运算符::,该运算符表示将使用变量的全局版本。

静态持续性、内部链接性

static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。即变量只能在其所属的文件中使用。

1
2
3
4
5
6
7
8
9
10
//file1
int error = 20; //

-------------------
//file2
static int error = 5;//只在file2中可见
void froobish
{
cout << error;
}

静态存储持续性、无链接性

static限定符用于在代码块中定义的变量,将导致局部变量的存储持续性为静态的。这意味着虽然该变量只在该代码块中可用,但是它在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将保持不变。

如果初始化了静态局部变量,则程序只在启动时进行一次初始化。

说明符和限定符

有些被称为存储说明符或cv-限定符的c++关键字提供了其他有关存储的信息。下面是存储说明符

  • auto(在c++11中不再是说明符)

    c++11之前,可以使在声明中使用关键字auto指出变量是自动变量;但在c++11中,苏通用于自动类型判断

  • register

    关键字register用于在声明中指示寄存器存储,而在c++11中,它只是显式地指出变量是自动的

  • static

    关键字static被用在作用域为生个文件的声明中时,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的

  • extern

    关键字extern表明是引用声明,即声明引用在其他地方定义的变量。

  • thread_local(c++新增的)

    关键字thread_local指出变量的持续性与其所属线程的持续性相同

  • mutable

    关键字mutable表明,即使结构体(或类)变量为const,其某个成员也可以被修改。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct data
    {
    char name [30];
    mutable int accesses;
    ...
    };
    const data veep ={"Alice", 0, ...};
    strcpy(veep.name,"Bob");// not allowed
    veep.accesses++;//allowed

cv-限定符

  • const

    关键字const表明,内存被初始化后,程序不能再对它进行修改。

    在c++中,const限定度对默认存储类型稍有影响。在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。即在c++看来,全局const定义就像使用了static说明符一样。

  • volatile

关键字volatile表明,即使程序代码没有对内存单元进行修改,其值也可能发生改变。

存储方案和动态分配

使用new运算符初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
//为内置的标量类型分配存储空间并初始化
int *pi = new int (6);
double * pd = new double(99.99);

//初始化常规结构体和数组
struct where
{
double x;
double y;
double z;
}
where * one = new where{2.3, 4.6, 7.4};
int * ar = new int [4] {2, 4, 6, 2};

new失败时

引发异常std::bad_alloc

定位new运算符

通常,new复杂在堆里找一个能够满足要求的内存块。new运算符还有一种变体,被称为定位new运算符,能够让开发者指定要使用的位置。

要使用定位new特性,首先要包含头文件new,然后将new运算符用于提供了所需地址的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//演示new运算符的4种用法
#include<new>
struct chaff
{
char dross[20];
int slag;
}
char buffer1[50];
char buffer2[500];

int main()
{
chaff *p1, *p2;
int *p3, *p4;
// new运算符常规用法
p1 = new chaff;
p3 = new int[20];

//定位new运算符
p2 = new (buffer1) chaff;
p4 = new (buffer2) int [20];
...
}

不能使用delete来释放定位new运算符分配的内存

名称空间

在c++中,名称可以是变量、函数、结构、枚举、类以及类和结构的成员。当随着项目的增大,名称相互冲突的可能性也将增加。使用多个厂商的类库时,可能导致名称冲突。例如,两个库都定义了名为List、Tree和Node的库,但定义的方式不兼容。用户可能希望使用一个库的List类,而使用另一个库的Tree类,这种冲突被称为名称空间问题。

c++通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace Jack
{
double pail;
void fetch();
int pal;
struct Well{...};
}

namespace Jill
{
double bucket(double n){}
double fetch;
int pal;
struct Hill{...};
}


Jack::pail = 12.34;
Jill::Hill mole; // creat a type Hill structure

using声明和using编译指令

using声明使特定的标识符可用,using编译指令使整个名称空间可用

using声明由被限定的名称和它前面的关键字using组成。using声明将特定的名称添加到它所属的声明区域中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace Jill
{
double bucket(double n){}
double fetch;
int pal;
struct Hill{...};
}
char fetch;

int main ()
{
using Jill::fetch;
double fetch;//error!!
cin >> fetch; //read a value into Jill::fetch
cin >> ::fetch;// read a value into global fetch
}

using编译指令使所有的名称都可用。using编译指令由名称空间名和它前面的关键字using namespace组成,它使名称空间中的所有名称都可用,而不需要使用作用域解析运算符。

using编译指令和using声明的比较

使用using编译指令导入一个名称空间的所有名称和使用多个using声明使不一样的。使用using声明时,就好像声明了相应的名称一样。如果某个名称已经在函数中声明了,则不能使用using声明导入相同的名称。

使用using编译指令时,将进行名称解析,就像在包含using声明和名称空间本身的最小声明区域中声明了名称一样。如果使用using编译指令导入了一个已经在函数中声明的名称,则局部名称将隐藏名称空间名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace Jill
{
double bucket(double n){}
double fetch;
int pal;
struct Hill{...};
}

char fetch; // global namespace
int main()
{
using namespace Jill;
Hill Thrill;//create a type Jill::Hill strcuture
double water = bucket(2);//use Jill::bucket()
double fetch;//not an error,hides Jill::fetch
cin >> fetch;//read a value into the local fetch
cin >> ::fetch;//read a value into the global fetch
cin >> Jill::fetch;//read a value into the Jill fetch
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!