【C++ Primer Plus】学习笔记--第9章 内存模型和名称空间


9.1 单独编译

P300

分解程序

  • 头文件:包含结构声明和使用这些结构的函数原型
  • 源代码文件:包含于结构有关的函数代码
  • 源代码文件:包含main()与使用这些函数的函数代码。

头文件中常包含的内容

  • 函数原型
  • 使用#define或const定义的符号常量
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数

请不要将函数定义变量声明放到头文件中。

在包含自己的头文件时,应使用引号源代码目录查找)而不是尖括号标准头文件的主机系统的文件系统查找)。

头文件管理

在同一个文件中只能将同一个头文件包含一次

避免多次包含:

#ifndef COORDIN_H_
#define COORDIN_H_
// place include file contents here
#endif

#ifndef(if not define) 语言移植性好,但可能命名冲突
#pragma once,效率更高,但不支持跨平台

#pragma once
// place include file contents here

解释】:当第一次包含时,名称COORDIN_H_没有定义,执行#define COORDIN_H_和下面的内容;第二包含时,由于COORDIN_H_已经被定义,从而跳到#endif后面的一行上。

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

P304

作用域和链接性

作用域:描述了名称在文件的多大范围内可见。
链接域:描述了名称如何在不同单元间共享。

作用域为局部变量只在定义它的代码块中可用。代码块是由花括号括起来的一系列语句。

自动存储持续性

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

注意】:当局部代码块定义同名变量,新定义隐藏了以前的定义,新定义可见,旧定义暂时不可见。在程序该代码块,原来的定义又重新可见

代码如下

	int a = 10;
	{
    
      
		int a = 20; 
		cout << a << endl;
	}
	cout << a << endl;

运行结果
在这里插入图片描述

register变量,早期它建议编译器使用CPU寄存器来存储自动变量。由于现在编译器的优化,使用register关键字的唯一原因是:指出程序员想使用一个自动变量,这个变量名称可能与外部变量相同。

静态持续变量

编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。

注意】:如果没有显示地初始化静态持续变量,编译器将把它设置为0

静态存储持续变量有3种链接性

  • 外部链接性(可在其他文件中访问)
  • 内部链接性(只能在当前文件中访问)
  • 无链接性(只能在当前函数或代码块中访问)
    代码如下:
int global = 1000; // static duration, external linkage
static int one_file = 50; // static duration, internal linkage

void func1(){
    
    
	static int count = 0; //static duration, no linkage
}

外部链接性

  • 定义声明(简称定义),给变量分配存储空间
  • 引用声明(简称声明),不给变量分配存储空间,它引用已有的变量。引用声明使用关键字extern,不进行初始化,否则报错多重定义

注意】:在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量。使用该变量的其他文件必须使用关键字extern 声明它

//file1.cpp
double warming = 0.2;

//file2.cpp
extern double warming;

全局变量和局部变量命名冲突时,::warming 更好的选择,也更安全

double warming = 0.5; //local variable
cout << warming;
cout << ::warming; // global variable

建议】:通常情况下,应尽量使用局部变量,达到数据隔离,避免对数据进行不必要的访问。

内部链接性

static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的

两个文件声明的常规外部变量相同,静态变量将隐藏常规外部变量

// file1.cpp
int errors = 20; //external declaration

// file2.cpp
static int errors = 5; // known to file2 only

无链接性

将static限定符用于在代码块中定义的变量,为无链接性的静态局部变量。

注意】:如果初始化了静态局部变量,则程序只能在启动时,进行一次初始化。以后再调用该函数,不会再被初始化。

示例代码如下:

//static.cpp -- using a static local variable
#include <iostream>
using namespace std;
const int ArSize = 10;
void strcount(const char* str);

int main()
{
    
    
	char input[ArSize]{
    
    };
	char next{
    
    };

	cout << "Enter a line:\n";
	cin.get(input, ArSize);

	while (cin)
	{
    
    
		//处理输入可能长于目标数组的方法
		cin.get(next);
		while (next != '\n')
			cin.get(next); // dispose of remainder
		strcount(input);
		cout << "Enter next line(empty line to quit):\n";
		cin.get(input, ArSize);
	}
	cout << "Bye!\n";
	system("pause");
	return 0;
}

void strcount(const char* str)
{
    
    
	int count{
    
    };
	static int total{
    
    };
	cout << str << endl;
	while (*str++)
		count++;

	total += count;
	cout << count << "characters\n";
	cout << total << "characters total\n";
}

每次函数被调用时,count都被初始化为0,而静态变量 total 只有第一次运行时被初始化为0

const限定符

限定符volatile:编译器不会对该变量进行优化,从而可以提供对特殊地址的稳定访问。
mutable:即使结构体或者类const类型,但被mutable修饰的成员也可以被修改

struct student
{
    
    
	char name[30];
	mutable int accesses; // 使得成员变量可修改
};
const student veep{
    
    "Joye", 2};
strcpy(veep.name, "Tom"); // not allowed
veep.accesses++; // allowed

const全局变量链接性为内部的。在c++看来,全局const定义就像使用了static说明符一样。由于const的链接性为内部的,因此可以在所有文件中使用相同的声明

技巧】:为了所有文件共享一组常量,需要将常量定义放在头文件中

语言链接性

在C语言中,一个名称只对应一个函数,C语言编译器可能将spiff函数名翻译为_spiff,称为C语言链接性

在C++中,由于函数重载和多态同一个名称可能对应着多个函数,C++编译器执行名称矫正和名称修饰。可能将spiff(int)转化为_spiff_i,spiff(double, double) 转化为_spiff_d_d,称为C++语言链接性

extern "C" void spiff(int); // 使用C语言链接性
extern "C++" void spiff(int); //使用C++语言链接性

存储方案和动态分配

动态内存由运算符new和delete控制,而不是由作用域和链接性规则控制。

通常,编译器使用三块独立的内存

  • 用于存放静态变量
  • 用于自动变量
  • 用于动态存储

定位 new 运算符

定位new运算符,能够在指定内存地址存放变量。不管它是否已经被使用,新值直接覆盖在旧值上面。

定位new使用方法:

#include <new>
char buffer1[300];
int *p1 = new(buffer1) int[20]; // place in array in buffer1

double *buffer2 = new double[20];
int *p2 = new(buffer2) int[20]; 

注意】:buffer1的内存为静态内存,则不能delete;buffer2的内存是使用常规的new 创建的,虽然可以使用delete释放,但buffer2和p2 只能释放一个。建议不要用delete释放定位new

9.3 名称空间

P324

通过定义一种新的声明区域来创建命名的名称空间,提供一个名称的区域。

通过作用域解析运算符 :: 访问该名称。

名称空间:

namespace Jack{
    
    
	double pail;
	void fetch();
}

Jack::pail = 2.22;

注意】:名称空间,不能位于代码块中。在名称空间中声明的名称的链接性为外部的

using声明和using编译指令

C++提供了两种机制 using声明using 编译指令 来简化对名称空间中名称的使用。

using声明使一个名称可用, 而using编译指令使所有名称可用。

using Jack; // using声明
using namespace Jack; // using编译指令

using声明比使用using编译指令更安全。
using声明只导入指定的名称。如果该名称与局部名称发生冲突,编译器发出提示。
using编译指令导入所有名称,包含可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本, 而编译器并不会发出警告。

技巧】:尽量避免使用using编译指令,推荐使用作用域解析运算符或using声明。

using namespace std; // 避免随意使用

// 建议使用
using std::cout;  // using声明
std::cout << "Hello!" << std::endl; //作用域解析运算符

未命名的名称空间

不能在未命名名称空间所属文件之外的其他文件使用该名称空间中的名称。

未命名的名称空间为链接性为内部的静态变量的替代品

static int counts; //static storage
namespace
{
    
    
	int counts;
}

名称空间指导原则(编程理念)

  • 使用已命名的名称空间中的声明变量,而不是使用外部全局变量。
  • 使用已命名的名称空间中的声明变量,而不是使用静态全局变量。
  • 如果开发了一个函数库或者类库,将其放在一个名称空间中。 C++当前提倡将标准函数库放在命名空间std中,这种做法扩展到了C语言中的函数。
  • 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计。
  • 不要在头文件中使用using编译指令。 原因:首先,这样做掩盖了要让哪些名称可用;另外,包含头文件的顺序可能影响程序的行为。如果非要使用编译指令using,应将其放在所有预处理器编译指令#include之后。
  • 导入名称时,首选使用作用域解析运算符或者using声明方法。重要
  • 对于using声明,首选将其作用域设置为局部而不是全局。

猜你喜欢

转载自blog.csdn.net/ZR_YHY/article/details/111998403