【C++ Primer Plus】第9章 内存模型和名称空间

9.1 多文件程序

一个文件(头文件)包含了用户定义类型的定义;另一个文件包含操纵用户定义类型的函数的代码。这两个文件组成了一个软件包,可用于各种程序中。
头文件中常包含的内容:

  1. 函数原型。
  2. 使用#define或const定义的符号常量。
  3. 结构声明。
  4. 类声明。
  5. 模板声明。
  6. 内联函数。

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

  1. 如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;
  2. 但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器)。
  3. 如果没有在当前工作目录找到头文件,则将在标准位置查找。
  4. 在IDE中,不要将头文件加入到项目列表中,也不要在源代码文件中使用#include来包含其他源代码文件。
// CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
project(PrimerPlus)

set(CMAKE_CXX_STANDARD 11)

include_directories(chapter_9)	// 头文件路径

// 将file1和file2一起编译链接,只要两个文件包含相同的头文件,且file2中定义的函数在头文件中声明,则file1可以调用file2的函数且不需要声明
add_executable(file1 chapter_9/file1.cpp chapter_9/file2.cpp)
// coordin.h -- structure templates and function prototypes
// structure templates
// 仅当以前没有使用预处理器编译指令#define定义名称COORDINH时,才处理#ifndef和#endif之间的语句:
#ifndef PRIMERPLUS_COORDIN_H	// PRIMERPLUS是工程的名字
#define PRIMERPLUS_COORDIN_H

#endif

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

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

9.2.1 作用域和链接

  1. 链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。
  2. 自动变量的名称没有链接性,因为它们不能共享。
  3. 执行到代码块时,将为变量分配内存,但其作用域的起点为其声明位置。

9.2.2 自动存储持续性

如果有两个同名的变量(一个位于外部代码块中,另一个位于内部代码块中),情况将如何呢?
新的定义隐藏了(hide)以前的定义,新定义可见,旧定义暂时不可见。在程序离开该代码块时,原来的定义又重新可见。如下图所示

在C++11中,关键字auto用于自动类型推断。
但在C语言和以前的C++版本中,auto的含义截然不同,它用于显式地指出变量为自动存储。

  1. 自动变量的初始化:

可以使用任何在声明时其值为已知的表达式来初始化自动变量。

  1. 自动变量和栈:
    1. 由于自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用的方法是留出一段内存,并将其视为栈,以管理变量的增减。
    2. 之所以被称为栈,是由于新数据被象征性地放在原有数据的上面(也就是说,在相邻的内存单元中,而不是在同一个内存单元中),当程序使用完后,将其从栈中删除。
    3. 栈的默认长度取决于实现,但编译器通常提供改变栈长度的选项。程序使用两个指针来跟踪栈,一个指针指向栈底——栈的开始位置,另一个指针指向堆顶 ——下一个可用内存单元。当函数被调用时,其自动变量将被加入到栈 中,栈顶指针指向变量后面的下一个可用的内存单元。函数结束时,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。
    4. 栈是LIFO(后进先出)的,即最后加入到栈中的变量首先被弹出。这种设计简化了参数传递。函数调用将其参数的值放在栈顶,然后重新设置栈顶指针。被调用的函数根据其形参描述来确定每个参数的地址。
  1. 寄存器变量
    1. 在C++11中,关键字register只是显式地指出变量是自动的。只能用于原本就是自动的变量。
      使用它的唯一原因是,指出程序员想使用一个自动变量,这个变量的名称可能与外部变量相同。
      这与auto以前的用途完全相同。然而,保留关键字register的重要原因是,避免使用了该关键字的现有代码非法。
    2. 在C语言中,关键字register它建议编译器使用CPU寄存器来存储自动变量,旨在提高访问变量的速度。register int count_fast;

9.2.3 静态持续变量

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

  1. 外部链接性(可在其他文件中访问)在代码块的外面声明它
  2. 内部链接性(只能在当前文件中访问)在代码块的外面声明它,并使用static限定符
  3. 无链接性(只能在当前函数或代码块中访问)在代码块内声明它,并使用static限定符
int global = 1000;          // static duration, external linkage
static int one_file = 50;   // static duration, internal linkage
int main()
{
    ...
}
void funct1(int n)
{
    static int count = 0;   // static duration, no linkage
    // 即使在 funct1( )函数没有被执行时,count也留在内存中。
    int llama = 0;
    ...
}

静态变量初始化:

  1. 静态初始化包括:零初始化int a; 和常量表达式初始化int b=5;
  2. 零初始化:如果没有显式地初始化静态变量,编译器将把它设置为0。
  3. 在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0。
  4. 空指针用0表示,但是内部可能采用非零表示。指针变量被初始化为相应的内部表示。
  5. 静态初始化在编译器处理文件(翻译单元)时初始化变量。动态初始化意味着变量将在编译后初始化。
  6. C++11新增了关键字constexpr,这增加了创建常量表达式的方式。
#include <cmath>
int x; 									// zero-initialization
int y = 5; 								// constant-expression initialization
long z = 13 * 13; 						// constant-expression initialization
const double pi = 4.0 * atan(1.0); 		// dynamic initialization
int enough = 2 * sizeof (long) + 1; 	// constant expression initialization

9.2.4 静态持续性、外部链接性

C++有“单定义规则”(One Definition Rule,ODR),该规则指出,变量只能有一次定义。
C++提供了两种变量声明:

  1. 一种是定义声明(定义),它给变量分配存储空间;
  2. 一种是引用声明(声明),它不给变量分配存储空间,因为它引用已有的变量。
  3. 引用声明使用关键字extern,且不进行初始化;否则,声明为定义,导致分配存储空间。
  4. 如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern声明它。
double up; 				// definition, up is 0
extern int blem; 		// blem defined elsewhere
extern char gr = 'z'; 	// definition because initialized

如果在函数中声明了一个与外部变量同名的变量,结果将如何呢?
这种声明将被视为一个自动变量的定义,当程序执行自动变量所属的函数时,该变量将位于作用域内。

作用域解析运算符(::) 放在变量名前面时,该运算符表示使用变量的全局版本。

int a = 10;
void main(void)
{
    int a = 5;
    cout << a << endl;	// a = 5;
    cout << ::a << endl;// a = 10;
}

外部存储尤其适于表示常量数据,因为这样可以使用关键字const 来防止数据被修改。

const char * const months[12] =
{
"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October",
"November", "December"
};
// 第一个const防止字符串被修改,
// 第二个const确保数组中每个指针始终指 向它最初指向的字符串。

9.2.5 静态持续性、内部链接性

  1. 将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的,只能在其所属的文件中使用。
  2. 但如果文件定义了一个静态外部变量,其名称与另一个文件中声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量。
  3. 在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量。使用该变量的其他文件必须使用关键字extern声明它。
// file1
int errors = 20; 		// external declaration
...
---------------------------------------------
// file2
static int errors = 5; 	// known to file2 only
// int errors = 10;		//这种做法是错误的,违反了单定义规则
void froobish()
{
cout << errors; 		// uses errors defined in file2
...

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

  1. 将static限定符用于在代码块中定义的变量。
  2. 这意味着虽然该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。
  3. 如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。
// 使用cin.get(next)读取行输入后的字符。
// 如果next是换行符,则说明 cin.get(input, ArSize)读取了整行;
// 否则说明行中还有字符没有被读取。 随后,程序使用一个循环来丢弃余下的字符。
// static.cpp -- using a static local variable
#include <iostream>
const int ArSize = 20;              // constants
void strcount(const char * str);    // function prototype
int main()
{
    using namespace std;
    char input[ArSize];
    char next;
    cout << "Enter a line:\n";
    cin.get(input, ArSize);
    while (cin)
    {
        cin.get(next);          // 使用cin.get(next)读取行输入后的字符。
        while (next != '\n')    // string didn't fit!
            cin.get(next);      // dispose of remainder
        strcount(input);
        cout << "Enter next line (empty line to quit):\n";
        cin.get(input, ArSize); // 使用get(char *, int)读取空行将导致cin为false。
    }
    cout << "Bye\n";
    return 0;
}
void strcount(const char * str)
{
    using namespace std;
    static int total = 0;   // static local variable
    int count = 0;          // automatic local variable
    cout << "\"" << str <<"\" contains ";
    while (*str++)          // go to end of string
        count++;
    total += count;
    cout << count << " characters\n";
    cout << total << " characters total\n";
}

9.2.7 说明符和限定符

存储说明符:

  1. auto(在C++11中不再是说明符): 在C++11之前,可以在声明中使用关键字auto指出变量为自动变量;在C++11中,auto用于自动类型推断。
  2. register:在声明中指示寄存器存储,而在C++11中,它只是显式地指出变量是自动的。
  3. static:被用在作用域为整个文件的声明中时,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的。
  4. extern:表明是引用声明,不可初始化,即声明引用在其他地方定义的变量。
  5. thread_local(C++11新增的):指出变量的持续性与其所属线程的持续性相同。thread_local变量之于线程,犹如常规静态变量之于整个程序。在同一个声明中不能使用多个说明符,但thread_local除外,它可与static或extern结合使用。
  6. mutable:即使结构(或类)变量为const,其某个成员也可以被修改。
struct data
{
    char name[30];
    mutable int accesses;	// 即使声明一个const的结构,也可以对其中的accesses进行修改
    ...
};
    const data veep = {"Claybourne Clodde", 0, ... };
    // strcpy(veep.name, "Joye Joux"); 	// not allowed
    veep.accesses++; 					// allowed

cv限定符: (cv表示const和volatile)

  1. volatile:即使程序代码没有对内存单元进行修改,其值也可能发生变化。该关键字的作用是为了改善编译器的优化能力。
  2. const:内存被初始化后,程序便不能再对它进行修改。
    C++11中,在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。 内部链接性还意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中的原因。这样,只要在两个源代码文件中包括同一个头文件,则它们将获得同一组常量。
// file1
int a = 5;				// 链接为外部,extern能省略。
const int b = 6;		// 链接为内部,只能file1文件使用
extern const int f = 10;// 链接为外部,extern不能省略。
static const int c = 7;	// 链接为内部,只能file1文件使用,c不能改变
static int e = 9;		// 链接为内部,只能file1文件使用,c可以改变
{
    static int d = 8;	// 无链接
}
--------------------------------
// file2
extern int a;		// 引用声明
extern const int f;	// 引用const声明
--------------------------------
// xxx.h
// 只要在两个源代码文件中包括同一个头文件,则它们将获得同一组常量。
// const全局变量的链接性为内部,不违背“单定义规则”
const int g = 11;
const int h = 12;

9.2.8 函数和链接性

  1. 可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的。
  2. 可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用。必须同时在原型和函数定义中使用该关键字:
    这意味着该函数只在这个文件中可见,还意味着可以在其他文件中定义同名的的函数。
    和变量一样,在定义静态函数的文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍将使用静态函数。
  3. 内联函数的定义可放在头文件中。

9.2.9 语言链接性

链接程序要求每个不同的函数都有不同的符号名(内部表示)。

  1. C语言中,一个名称只对应一个函数。
  2. C++中,同一个名称可能对应多个函数,必须将这些函数翻译为不同的符号名称。因此,C++编译器执行名称矫正或名称修饰,为重载函数生成不同的符号名称。
// 在C++程序中使用C库中预编译的函数,可以用函数原型来指出要使用的约定:
extern "C" void spiff(int); 	// use C protocol for name look-up
extern void spoff(int); 		// use C++ protocol for name look-up
extern "C++" void spaff(int); 	// use C++ protocol for name look-up

9.2.10 存储方案和动态分配

通常,编译器使用三块独立的内存:一块用于静态变量(可能再细分),一块用于自动变量,另外一块用于动态存储。
存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。

1. 使用new运算符初始化

C++98,如果要为内置的标量类型(如int或double)分配存储空间并初始化,可在类型名后面加上初始值,并将其用括号括起:

int *pi = new int (6); 				// *pi set to 6
double * pd = new double (99.99); 	// *pd set to 99.99

在C++11中,可初始化常规结构或数组,还可将列表初始化用于单值变量:

struct where {double x; double y; double z;};
where * one = new where {2.5, 5.3, 7.2}; 		// C++11
int * ar = new int [4] {2,4,6,7}; 				// C++11
int *pin = new int {}; 				// *pi set to 0
double * pdo = new double {99.99}; 	// *pd set to 99.99

2. new失败

new可能找不到请求的内存量。在最初的10年中,C++在这种情况下让new返回空指针,但现在将引发异常std::bad_alloc。

3.new:运算符、函数和替换函数

void * operator new(std::size_t); 		// used by new
void * operator new[](std::size_t); 	// used by new[]

void operator delete(void *);
void operator delete[](void *);

// 运算符重载,std::size_t是一个 typedef
int * pi = new int;
int * pi = new(sizeof(int));

int * pa = new int[40];
int * pa = new(40 * sizeof(int));

delete pi;
delete (pi);

4.定位new运算符

通常,new负责在堆(heap)中找到一个足以能够满足要求的内存块。
new运算符还有另一种变体,被称为定位(placement)new运算符,它让您能够指定要使用的位置。

  1. 要使用定位new特性,首先需要包含头文件new,它提供了这种版本的new运算符的原型;#include <new>
  2. 然后将new运算符用于提供了所需地址的参数。
  3. 使用定位new运算符时,变量后面可以有方括号,也可以没有。
#include <new>	// 提供了这种版本的new运算符的原型
struct chaff
{
    char dross[20];
    int slag;
};
char buffer1[50];	// 使用两个静态数组来为定位new运算符提供内存空间。
char buffer2[500];
int main()
{
    chaff *p1, *p2;
    int *p3, *p4;
    // first, the regular forms of new
    p1 = new chaff; 	// place structure in heap
    p3 = new int[20]; 	// place int array in heap
    // now, the two forms of placement new
    p2 = new (buffer1) chaff; 	// place structure in buffer1
    p4 = new (buffer2) int[20]; // place int array in buffer2
    ...

标准定位new 调用一个接收两个参数的new()函数:
定位new函数不可替换,但可重载。它至少需要接收两个参数,其中第一个总是std::size_t,指定了请求的字节数。

int * pi = new int; 			// invokes new(sizeof(int))
int * p2 = new(buffer) int; 	// invokes new(sizeof(int), buffer)
int * p3 = new(buffer) int[40]; // invokes new(40*sizeof(int), buffer)

9.3 名称空间

潜在作用域: 变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域比声明区域小,这是由于变量必须定义后才能使用。

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

  1. 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。
  2. 任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。
  3. 名称空间是开放的(open),即可以把名称加入到已有的名称空间中。
namespace Jack 
{
    double pail; 	// variable declaration
    void fetch(); 	// function prototype
    int pal; 		// variable declaration
    struct Well { ... }; // structure declaration
}
namespace Jill 
{
    double bucket(double n) { ... } // function definition
    double fetch; 			// variable declaration
    int pal; 				// variable declaration
    struct Hill { ... }; 	// structure declaration
}
namespace Jill 
{
	char * goose(const char *);	// 名称空间中添加变量
}
namespace Jack 
{
	void fetch()	// 函数定义
	{...} 
}
Jack::pail = 12.34; // use a variable
Jill::Hill mole; 	// create a type Hill structure
Jack::fetch(); 		// use a function

9.3.1 using声明和using编译指令

  1. using编译指令使整个名称空间可用using namespace std;
  2. using声明使特定的标识符可用,using声明由被限定的名称和它前面的关键字using组成:using Jill::fetch;
  3. 在函数的外面使用using声明时,将把名称添加到全局名称空间中。
  4. 编译器不允许同时使用两个using声明相同名称变量,可以使用作用域解析运算符。
// using声明由被限定的名称和它前面的关键字using组成:
using Jill::fetch; 	// a using declaration
// using声明将特定的名称添加到它所属的声明区域中
namespace Jill {
    double bucket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
char fetch;
int main()
{
    using Jill::fetch; 	// put fetch into local namespace
    double fetch; 		// Error! Already have a local fetch
    cin >> fetch; 		// read a value into Jill::fetch
    cin >> ::fetch; 	// read a value into global fetch
    ...
}
// 编译器不允许同时使用两个using声明相同名称变量,可以使用作用域解析运算符。
jack::pal = 3;
jill::pal =10;
// 编译器不允许同时使用两个using声明相同名称变量
// using jack::pal;	
// using jill::pal;

9.3.2 using编译指令和using声明之比较

假设名称空间和声明区域定义了相同的名称:

  1. 如果试图使用using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。
  2. 如果使用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。
namespace Jill {
    double bucket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
char fetch; // global namespace
int main()
{
    using namespace Jill; 	// import all namespace names
    Hill Thrill; 			// create a type Jill::Hill structure
    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 global fetch
    cin >> Jill::fetch; 	// read a value into Jill::fetch
    ...
}
int foom()
{
    // Hill top; 			// ERROR
    // Jill::Hill crest; 	// valid
    using Jill::Hill crest;
}

9.3.3 嵌套式名称空间

  1. 可以用嵌套式名称空间来创建一个包含常用using声明的名称空间。
  2. 可以在名称空间中使用using编译指令和using声明。
  3. 可以给名称空间创建别名。
// 可以用嵌套式名称空间来创建一个包含常用using声明的名称空间:
namespace elements
{
    namespace fire
    {
        int flame;
        ...
    }
    float water;
}
// 使用下面的using编译指令使内部名称可用:
using namespace elements::fire;

// 在名称空间中使用using编译指令和using声明:
namespace myth
{
    using Jill::fetch;
    using namespace elements;
    using std::cout;
    using std::cin;
}
std::cin >> myth::fetch;	// 可以这样访问fetch
std::cout << Jill::fetch;	// 也可以这样访问fetch

// 可以给名称空间创建别名:
namespace my_very_favorite_things { ... };
namespace mvft = my_very_favorite_things;
namespace MEF = myth::elements::fire;
using MEF::flame;

9.3.4 未命名的名称空间

  1. 这就像后面跟着using编译指令一样,也就是说,在该名称空间中声明的名称的潜在作用域为:从声明点到该声明区域末尾。从这个方面看,它们与全局变量相似。
  2. 不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称。
  3. 这提供了链接性为内部的静态变量的替代品。
static int counts;	// 全局静态变量,链接为内部。
// 可以用下面的替代
namespace
{
	int counts; 	// static storage, internal linkage
}

9.4 总结

  1. C++鼓励程序员在开发程序时使用多个文件。一种有效的组织策略是,使用头文件来定义用户类型,为操纵用户类型的函数提供函数原型;并将函数定义放在一个独立的源代码文件中。头文件和源代码文件一起定义和实现了用户定义的类型及其使用方式。最后,将main( )和其他使用这些函数的函数放在第三个文件中。
  2. C++的存储方案决定了变量保留在内存中的时间(储存持续性)以及程序的哪一部分可以访问它(作用域和链接性)。自动变量是在代码块(如函数体或函数体中的代码块)中定义的变量,仅当程序执行到包含定义的代码块时,它们才存在,并且可见。自动变量可以通过使用存储类型说明符register或根本不使用说明符来声明,没有使用说明符时,变量将默认为自动的。register说明符提示编译器,该变量的使用频率很高,但C++11摒弃了这种用法。
  3. 静态变量在整个程序执行期间都存在。对于在函数外面定义的变量,其所属文件中位于该变量的定义后面的所有函数都可以使用它(文件作用域),并可在程序的其他文件中使用(外部链接性)。另一个文件要使用这种变量,必须使用extern关键字来声明它。对于文件间共享的变量,应在一个文件中包含其定义声明(无需使用extern,但如果同时进行初始化,也可使用它),并在其他文件中包含引用声明(使用extern且不初始化)。在函数的外面使用关键字static定义的变量的作用域为整个文件,但是不能用于其他文件(内部链接性)。在代码块中使用关键字static定义的变量被限制在该代码块内(局部作用域、无链接性),但在整个程序执行期间,它都一直存在并且保持原值。
  4. 在默认情况下,C++函数的链接性为外部,因此可在文件间共享;但使用关键字static限定的函数的链接性为内部的,被限制在定义它的文件中。
  5. 动态内存分配和释放是使用new和delete进行的,它使用自由存储区或堆来存储数据。调用new占用内存,而调用delete释放内存。程序使用指针来跟踪这些内存单元。
  6. 名称空间允许定义一个可在其中声明标识符的命名区域。这样做的目的是减少名称冲突,尤其当程序非常大,并使用多个厂商的代码时。可以通过使用作用域解析运算符、using声明或using编译指令,来使名称空间中的标识符可用。

9.5 复习题

2.using声明和using编译指令之间有何区别?

  1. using声明使得名称空间中的单个名称可用,其作用域与using所在的声明区域相同。
  2. using编译指令使名称空间中的所有名称可用。
  3. 使用using编译指令时,就像在一个包含using声明和名称空间本身的最小声明区域中声明了这些名称一样。

5、在一个文件中调用average(3, 6)函数时,它返回两个int参数的int平均值,在同一个程序的另一个文件中调用时,它返回两个int参数的 double平均值。应如何实现?
可以在每个文件中包含单独的静态函数定义。或者每个文件都在未命名的名称空间中定义一个合适的average( )函数,在该函数前加static。

9.6 编程练习

2、判断输入是否为空

  • 使用字符数组
const ArSize = 20;
char input[ArSize];
char next;
cout << "Enter a line:\n";
cin.get(input, ArSize);
while (cin)
{
    cin.get(next);          // 使用cin.get(next)读取行输入后的字符。
    while (next != '\n')    // string didn't fit!
        cin.get(next);      // dispose of remainder
    cout << input << endl;
    cout << "Enter next line (empty line to quit):\n";
    cin.get(input, ArSize); // 使用get(char *, int)读取空行将导致cin为false。
}
-------------------------------
char name[20];
cin.getline(name, 20);
if (strcmp(name, "") == 0)
    break;
  • 使用字符串
string input;
cout << "Enter a line:\n";
getline(cin, input);
while (input != "") 	// 这里注意
{
    strcount(input);
    cout << "Enter next line (empty line to quit):\n";
    getline(cin, input);
}
------------------
string fullname;
getline(cin, fullname);
if (strcmp(name, "") == 0)
    break;
// 3、编写一个程序,使用定位new运算符将一个包含两个这种结构的数组放在一个缓冲区中。
// 然后,给结构的成员赋值(对于char数组,使用函数strcpy( )),并使用一个循环来显示内容。
// 一种方法是像程序清单 9.10那样将一个静态数组用作缓冲区;
// 另一种方法是使用常规new运算符来分配缓冲区。
#include <iostream>
#include <cstring>
#include <new>
using namespace std;
struct chaff
{
    char dross[20];
    int slag;
};
const int BUF = 200;
char buffer[BUF];
void show(const chaff &p);
int main(void)
{
    chaff * pd1 = new chaff[2];
    chaff * pd2 = new(buffer) chaff[2];  // 使用定位new运算符
    char dross[20];
    int slag;
    for (int i=0; i<2; i++)
    {
        cout << "#" << i+1 << " :" << endl;
        cout << "Enter the dross:";
        cin.getline(dross, 20);
        cout << "Enter the slag:";
        cin >> slag;
        cin.get();  // 这里要注意,消耗回车
        strcpy(pd1[i].dross, dross);
        strcpy(pd2[i].dross, dross);
        pd1[i].slag = slag;
        pd2[i].slag = slag;
    }
    for (int i=0; i<2; i++)
    {
        show(pd1[i]);
        show(pd2[i]);
    }
    delete [] pd1;  // 这里只能释放动态
    return 0;
}
void show(const chaff &p)   // 这里用结构体引用
{
    cout << "The dross is: " << p.dross << endl;
    cout << "The slag is:" << p.slag << endl;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值