【捡起C++】内存模型和名称空间

单独编译

​ C++允许甚至鼓励将组件函数放在独立的文件中。可以单独编译这些文件,然后链接成可执行的程序。 如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。

​ 头文件中常包含的内容:

  • 函数原型
  • 使用#define 或 const 定义的符号常量
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数
//coordin.h -- structure templates and function prototypes
//structure templates
#ifndef COORDIN_H_
#define COORDIN_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 // !COORDIN_H_
//file1.cpp -- example of a three-file program
#include <iostream>
#include "coordin.h"

using namespace std;

int main() {
	rect rplace;
	polar pplace;
	cout << "Enter the x and y values:";
	while (cin >> rplace.x >> rplace.y){
		pplace = rect_to_polar(rplace);
		show_polar(pplace);
		cout << "Next two numbers (q to quit): ";
	}
	cout << "Bye!\n";
	return 0;
}
// file2.cpp -- contains functions called in file1.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.y, xypos.x);
	return answer;
}


//show polar coordinates, converting angle to degrees 
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 << " degrees\n";
}
寄存器变量

​ 关键字register最初是由C语言引入的,他建议编译器使用CPU寄存器来存储自动变量:

register int count_fast;// request for a register variable

单定义规则

​ 该规则指出,变量只能有一次定义。为满足这种需求,C++提供了两种变量声明,一种是定义生命(defining declaration)或简称为定义(definition),它给变量分配存储空间;另一种是引用声明(referencing declaration)或简称为声明(declaration),他不给变量分配存储空间。

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

//file01.cpp
extern int cats = 20; // definition because of initialization
int dogs = 22;        // also a definition
int fleas;            // also a definition

...

//file02.cpp
//use cats and dogs from file01.cpp
extern int cats;
extern int dogs;
...
    
//file98.cpp
// use cats, dogs, and fleas from file01.cpp
extern int cats;
extern int dogs;
extern int fleas;
...

​ 单定义规则并非意味着不能有多个变量的名称相同。在不同函数中声明的同名自动变量是彼此独立的,它们都有自己的地址。


​ C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。为了将程序分为许多文件,则需要在文件中共享代码,例如一个文件的代码可能需要另一个文件中中定义的变量。

​ 为了支持分离式编译,C++允许将声明和定义分离开来。变量的声明规定了变量的类型和名字,即使一个名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。定义则负责创建与名字关联的实体,定义还申请存储空间。

​ 如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显式地初始化变量:

extern int i;  //声明i而非定义
int j;         //声明并定义i

但我们也可以给由extern关键字标记的变量赋一个初始值,但这样就不是一个声明了,而是一个定义:

extern int v = 2;
int v = 2;     //这两个语句效果完全一样,都是v的定义

注意: 变量能且只能被定义一次,但是可以被声明多次。

//external.cpp -- external variables
//compile with support.cpp
#include <iostream>

using namespace std;

//external variable
double warming = 0.3;

void update(double dt);
void local();
int main() {
	
	cout << "Global warming is " << warming << " degrees.\n";
	update(0.1);
	cout << "Global warming is " << warming << " degrees.\n";
	local();
	cout << "Global warming is " << warming << " degrees.\n";
	return 0;
}
//support.cpp -- external variables
//compile with external.cpp
#include <iostream>

using namespace std;
//external variable
extern double warming; // use warming from another file	

void update(double dt);
void local();

void update(double dt) {
	extern double warming;
	warming += dt;
	cout << "Updating global warming to " << warming;
	cout << " degrees.\n";
}

void local() {
	double warming = 0.8;
	cout << "Local warming = " << warming << " degrees.\n ";
	cout << "But global warming = " << ::warming << " degrees.\n ";
}

​ 另外,函数update()使用关键字extern重新声明了变量warming,这个关键字的意思是**,通过这个名称使用在外部定义的变量。**

​ local()函数表明,定义与全局变量同名的局部变量后,局部变量会隐藏全局变量。

​ 如果文件定义了一个静态外部变量,其名称与另一个文件声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量:

//file1
int errors = 20;   //external declaration
...
-------------------------------------
//file2
static int errors = 5; //known to file2 only
void froobish(){
    cout << errors;     //fails
}
//static.cpp -- using a static local variable
#include <iostream>
const int ArSize = 10;
void strcount(const char*);
int main() {
	using namespace std;
	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);
		}
		strcount(input);
		cout << "Enter next line (empty line to quit):\n";
		cin.get(input, ArSize);
		while (cin)
		{
			cin.get(next);
			while (next != '\n')
				cin.get(next);
			strcount(input);
			cout << "Enter next line (empty line to quit):\n";
			cin.get(input, ArSize);
		}
		cout << "Bye\n";
		return 0;
	}
}

void strcount(const char* str) {
	using namespace std;
	static int total = 0;
	int count = 0;
	cout << "\"" << str << "\" contains";
	while (*str++) {
		count++;
	}
	total += count;
	cout << count << " characters\n";
	cout << total << " characters total\n";
}

​ 方法cin.get(input, ArSize)将一直读取输入,直到到达行尾或读取了ArSize - 1个字符为止。他把换行符留在输入队列中。该程序使用cin.get(next)读取输入后的字符。如果next是换行符,则说明cin.get(input, ArSize)读取了整行;否则说明行中还有字符没有被读取。随后,程序用一个循环丢弃余下字符。

cv-限定符
  • const

  • volatile

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

mutable

​ 他来指出,即使结构(或类)变量为const,其某个成员也可以被修改。

struct data{
    char name[30];
    mutable int accesses;
    ...
};
const data veep = {"Claybourne Clodde", 0, ...};
strcpy(veep.name, "Joy");   //not allowed
veep.accesses++;  // allowed

​ veep的const限定符禁止程序修改veep的成员,但access成员的mutable说明符使得access不受这种限制。

再谈const

​ 在c++看来,全局const定义就像使用了static说明符一样。

const int fingers = 10;   //same as static const int fingers = 10;
int main(void){
    
}
C++在哪里查找函数

​ 假设在程序中的某个文件中调用一个函数,C++将到哪里去寻找该函数的定义呢?

  • 如果该文件中的函数指出该函数是静态的,则编译器将只在该文件中查找函数定义;
  • 否则,编译器将在所有的程序文件中查找。
  • 如果找到两个定义,编译器将发出错误消息,因为每个外部函数只能有一个定义。
  • 如果在程序文件中没有找到,编译器将在库中搜索。这意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数。
存储方案和动态分配

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

  1. 静态变量
  2. 自动变量
  3. 动态存储
使用new运算符初始化
int* pi = new int(6);  //set *pi = 6
double * pd = new double(99.99); //*pd set to 99.99
struct where{
    double x, y, z;
};
where* one = new where{2.5, 5.3, 7.2}; //c++11
int * ar = new int[4]{2,4,6,7}; // c++11
new失败

按c++标准的话,new失败会抛出bad_alloc异常,但是有些编译器对c++标准支持不是很好,比如vc++6.0中new失败不会抛出异常,而返回0.

//不支持c++标准的做法如下
double *ptr=new double[1000000];
if( 0 == ptr)

……处理失败……

//标准推荐做法一:
try
{
    double *ptr=new double[1000000];
}
catch(bad_alloc &memExp)
{
    //失败以后,要么abort要么重分配   
    cerr<<memExp.what()<<endl;
}

标准 C++ 亦提供了一个方法来抑制 new 抛出异常,而返回空指针:

int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针
if ( p == 0 ) // 如此这般,这个判断就有意义了
    return -1;

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

​ 运算符new 和 new[]分别调用如下函数:

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

​ 这些函数被称为分配函数(allocation function),它们位于全局名称空间中。同样,也有由delete 和 delete[] 调用的释放函数(deallocation function);

void * operator delete (void *);  //used by new
void * operator delete[](void *); //used by new[]

对于下面这样的基本语句:

int * pi = new int;

被转换为下面这样

int * pi = new (sizeof(int));

而下面的语句

int * pa = new int[40];

被转换为下面这样:

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

同样

delete pi;

被转换为下面这样:

delete (pi);

​ 有趣的是,c++将这些函数称为可替换的(replaceable)。这意味着如果您有足够的知识和意愿,可为new 和 delete 提供替换函数,并根据需要对其进行定制。例如,可定义作用域为类的替换函数,并对其进行定制,以满足该类的内存分配需求。在代码中,仍将使用new运算符,但他将调用 自己定义的new()函数

定位new运算符

​ 通常,new 负责在堆上找到一个足以能够满足要求的内存块。new 运算符还有一种变体,被称为定位(placement)new 运算符,他让您能够指定要使用的位置。可以使用这种特性来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。

​ 要使用 定位new 特性,首先需要包含头文件 new,它提供了这种版本的 new 运算符的原型;然后将new 运算符用于提供了所需地址的参数。 除了需要指定参数外,句法与常规new运算符相同。具体地说,使用定位new 运算符时,变量后面可以有方括号也可以没有。下面的代码演示了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;
	//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和定位new的一些重要差别。

//newplace.cpp -- using placement new
#include <iostream>
#include <new>        //for placement new
const int BUF = 512;
const int N = 5;
char buffer[BUF];   // chunk of memory
int main() {
	using namespace std;
	double* pd1, * pd2;
	int i;
	cout << "Calling new and placement new:\n";
	pd1 = new double[N];             //use heap
	pd2 = new (buffer)double[N];     //use buffer heap
	for ( i = 0; i < N; i++){
		pd2[i] = pd1[i] = 1000 + 20.0 * i;
	}
	cout << "Memory addresses:\n" << " heap: " << pd1
		<< " static: " << (void*)buffer << endl;
	cout << "Memory contents:\n";
	for (i = 0; i < N; i++) {
		cout << pd1[i] << " at " << &pd1[i] << "; ";
		cout << pd2[i] << " at" << &pd2[i] << endl;
	}
	
	cout << "\nCalling new and placement new a second time:\n";
	double* pd3, * pd4;
	pd3 = new double[N];             // find new address
	pd4 = new (buffer) double[N];    //overwrite old data
	for (i = 0; i < N; i++) {
		pd4[i] = pd3[i] = 1000 + 40.0 * i;
	}
	cout << "Memory contents:\n";
	for (i = 0; i < N; i++) {
		cout << pd3[i] << " at " << &pd3[i] << "; ";
		cout << pd4[i] << " at" << &pd4[i] << endl;
	}

	cout << "\nCalling new and placement new a second time:\n";
	delete[] pd1;
	pd1 = new double[N];
	pd2 = new (buffer + N * sizeof(double)) double[N];
	for (i = 0; i < N; i++) {
		pd2[i] = pd1[i] = 1000 + 60.0 * i;
	}
	cout << "Memory contents:\n";
	for (i = 0; i < N; i++) {
		cout << pd1[i] << " at " << &pd1[i] << "; ";
		cout << pd2[i] << " at" << &pd2[i] << endl;
	}
	delete[] pd1;
	delete[] pd3;
	return 0;
}

​ 第三次调用定位new运算符时,提供了一个从数组buffer开头算起的偏移量,因此将分配新的内存:

pd2 = new (buffer + N * sizeof(double)) double[N]; //offset of 40 bytes

​ 程序清单中没有使用delete来释放使用定位new运算符分配的内存。这个例子中,不能这么做。buffer指定的内存是静态内存,而delete只能用于这样的指针:指向常规new运算符分配的堆内存。也就是说,数组buffer位于delete的管辖区域之外,下面的语句将引发运行阶段的错误:

delete [] pd2; // won't work

​ 此外,如果buffer是使用常规new创建的,那么可以用delete释放。

标准定位new调用一个接收两个参数的new()函数:

int * p1 = 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);
名称空间

​ 在C++中,提供了名称空间工具,以便更好地控制名称的作用域。

​ 下面代码使用关键字namespace 创建了两个名称空间:Jack和Jill

namespace Jack{
    double pail;
    void fecth(); //function
    int pal;
    struct Well{
       
    };
}

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

​ 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。

​ 访问方式如下:

Jack::pail = 12.34;
Jill::Hill mole;
Jack::fetch();
namespace Jill{
    double bucket(double n){...}
    double fetch;
    struct Hill{...};
}
char fetch;             //global namespace
int main(){
    using namespace Jill;
    Hill Thrill;   // create a type Jill::Hill structure
    double water = bucket(2); //use Jill::bucket();
    double fetch;         // hides Jill::fetch
    cin >> fetch;         //read a value into local fetch
    cin >> ::fetch;       //read a value into global fetch
    cin >> Jill::fetch; 
}

名称空间示例

​ 多文件示例

//namesp.h
#include <string>
//create the pers and debts namespaces
namespace pers {
	struct Person {
		std::string fname;
		std::string lname;
	};
	void getPerson(Person&);
	void showPerson(const Person&);
}

namespace debts {
	using namespace pers;
	struct Debt {
		Person name;
		double amount;
	};
	void getDebt(Debt&);
	void showDebt(const Debt &);
	double sumDebts(const Debt ar[], int n);
}
//namesp.cpp
/*
	提供了头文件中的函数原型对应的定义。
	在名称空间中声明的函数名的作用域为整个名称空间,
	因此定义和声明必须位于同一个名称空间中。
*/
#include <iostream>
#include "namesp.h"
namespace pers {
	using std::cout;
	using std::cin;
	void getPerson(Person& rp) {
		cout << "Enter first name: ";
		cin >> rp.fname;
		cout << "Enter last name: ";
		cin >> rp.lname;
	}
	void showPerson(const Person& rp) {
		std::cout << rp.lname << ", " << rp.fname;
	}
}

namespace debts {
	void getDebt(Debt& rd) {
		getPerson(rd.name);
		std::cout << "Enter debt: ";
		std::cin >> rd.amount;
	}
	void showDebt(const Debt& rd) {
		showPerson(rd.name);
		std::cout << ": $" << rd.amount << std::endl;
	}
	double sumDebts(const Debt ar[], int n) {
		double total = 0;
		for (int i = 0; i < n; i++) {
			total += ar[i].amount;
		}
		return total;
	}
}
//usenmsp.cpp -- using namespace;
#include <iostream>
#include "namesp.h"
void other();
void another();
int main() {
	using debts::Debt;
	using debts::showDebt;
	Debt golf = { {"Benny", "Goatsniff"}, 120.0 };
	showDebt(golf);
	other();
	another();
	return 0;
}

void other() {
	using std::cout;
	using std::endl;
	using namespace debts;
	Person dg = { "Doodles", "Glister" };
	showPerson(dg);
	cout << endl;
	Debt zippy[3];
	int i;
	for (i = 0; i < 3; i++) {
		getDebt(zippy[i]);
	}
	for (i = 0; i < 3; i++) {
		showDebt(zippy[i]);
	}
	cout << "Total debt:$" << sumDebts(zippy, 3) << endl;
	return;
}

void another() {
	using pers::Person;
	Person collector = { "Milo", "Rightshift" };
	pers::showPerson(collector);
	std::cout << std::endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值