C++(学习笔记)内存模型和名称空间


前言

  本人在阅读C++ primer plus(第六版)的过程中做的笔记,写这篇文章既是为了分享,也是为了以后查阅。以下是本篇文章正式内容。


一、单独编译

  不要将函数定义或变量声明放在头文件中,这在简单情形下可能是可行的,但这样通常会造成麻烦。例如,如果在头文件中包含一个函数定义,然后在其它两个文件中(属于同一程序)包含该头文件,则同一个程序将包含两次这个函数的定义,除非是内联函数,否则将出错。头文件中常包含的内容:

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

  头文件的名称如果是包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找该头文件;如果包含在双引号中,则会首先在当前的工作目录或源代码目录中查找。
  在同一个文件中只能将同一个头文件包含一次,下面的代码可以防止包含同一个头文件两次而出错:

#ifndef HEAD_H_
#define HEAD_H_#endif

  省略号代表的是头文件的正式内容,假设该头文件是head.h。如果在同一个文件中包含了该头文件两次:

#include”head.h”#include”head.h”

  编译到第一个#include时,HEAD_H_未定义,因此对其定义;编译到第二个#include时,发现HEAD_H_已经定义过,在该头文件中会直接执行#endif后面的语句,避免了编译同一个头文件两次。
  C++允许每个编译器设计人员以他自己的喜好实现名称修饰,所以对于同一个函数,不同编译器中的名称修饰很有可能是不同的,在一个编译器中调用由另一个编译器定义的函数有极大概率会出错,如果有函数定义的源代码,建议在同一个编译器中重新编译以消除这种错误。

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

  C++有4中不同的方案来存储数据:

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

1.作用域和链接

  作用域描述了名称在文件的多大范围内可见;链接性描述了名称如何在不同文件间共享,链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。

2.静态持续变量

  静态存储持续性变量有3中链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)、无链接性(只能在当前函数或代码块中访问),这3种链接性都在整个程序执行期间存在。静态变量如果没有被显示初始化,编译器将把它设置为0,在默认情况下,静态数组和结构将每个元素或成员的所有位置都设置为0。
  要创建链接性为外部的静态持续变量,必须在代码块的外面声明它;要创建链接性为内部的静态持续变量,必须在代码块的外面声明它,且使用static限定符;要创建没有链接性的静态持续变量,必须在代码块的内部声明它且使用static限定符。例如:

int global = 1000;								//静态持续变量,外部链接性
static int_one_file = 50;						//静态持续变量,内部链接性
int main()
{}
void fun1(int n)
{
	static int count = 0;						//静态持续变量,无链接性}

5种变量存储方式:
5种变量存储方式

5种变量存储方式

3.静态持续性、外部链接性

  C++提供了两种变量声明,一种是定义声明或简称为定义,它给变量分配存储空间;另一种是引用声明或简称为引用,它不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,不进行初始化,否则将视为定义而导致分配空间。如果在各个文件中使用一个外部变量,只需在一个文件中进行定义,在需要使用这个变量的其他文件中使用关键字extern对其进行声明。

file01.cpp
int cats = 20;						//定义
int dogs = 22;						//定义
int fleas;							//定义
file02.cpp
extern int cats;					//引用
extern int dogs;					//引用
file03.cpp
extern int cats;					//引用
extern int dogs;					//引用
extern int fleas;					//引用

  file02.cpp声明了在file01.cpp中定义的cats和dogs,但是没有声明fleas,因此对其没有访问权限;file03.cpp声明了cats、dogs和fleas,因此都可以使用它们。
  可以看下面的两个文件:

external.cpp
double warming = 0.3;				//定义全局变量
void update(double dt);
void local();
int main()
{}
support.cpp
extern double warming;									//引用在另一个文件中定义的全局变量void update(double dt)
{
	extern double warming;								//使用全局变量
	warming += dt;}
void local()
{
	double warming = 0.8;								//定义局部变量,隐藏了同名的全局变量
	cout << “Local warming :<< warming << endl;
	cout << “Global warming :<< ::warming << endl;	//使用作用域解析运算符::,放在变量名前表示使用变量的全局版本
}

4.静态持续性、内部链接性

  如果文件定义了一个静态外部变量,其名称与另一个文件中定义的全局变量相同,则在该文件中静态变量将隐藏全局变量:
  //file1
  int errors = 20;  //全局变量
  ……
  //file2
  static int errors = 5;//静态外部变量,隐藏全局变量,只能由该文件访问
  ……

5.静态持续性、无链接性

  将static限定符用于在代码块中定义的变量,则该变量只能在该代码块中使用,而且该变量将一直存在,即使该代码块已经执行完毕。如果初始化了静态局部变量,则程序只在启动时进行一次初始化,之后再调用函数时将不会再进行初始化:
  void function()
  {
    …
    static int total = 0;
    …
    total++;
  }

6.const全局变量

  const全局变量的链接性是内部的,如果想在另一个文件中使用它,要用extern关键字来引用:
  //1.cpp
  const int states = 10; //与static const int states = 10相同
  //2.cpp
  extern const int states = 10;

7.函数的链接性

  和变量一样,函数也有链接性,C++不允许在一个函数中定义另一个函数,因此所有函数的存储持续性都自动为静态的,即在整个程序执行期间都一直存在。在默认情况下,函数的链接性为外部的,即可以在文件间共享。实际上,可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的,不过这是可选的(要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者是由链接程序搜索的库文件)。还可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用。必须同时在原型和函数定义中使用该关键字:
  static int private(double x);
  ……
  static int private(double x)
  {
     ……
  }
  这意味着这个函数只能在该文件中使用,可以在另一个文件中定义一个同名的函数。

8.定位new运算符

  通常,new负责在堆(heap)中寻找一个能够满足要求的内存块,new还有一种变体,用来分配指定位置的内存。
  要使用new的定位特性,需要包含头文件<new>。除需要指定参数外,句法与常规new运算符相同。
  #include <new>
  struct chaff
  {
    char dross[20];
    int slat;
  };
  char buffer1[50];
  char buffer2[500];
  int main()
  {
    chaff *p1, *p2;
    int *p3, *p4;
    p1 = new chaff;     //从堆(heap)中分配内存
    p3 = new int;      //从堆(heap)中分配内存
    p2 = new (buffer1) chaff; //从buffer1中分配内存
    p4 = new (buffer2) chaff; //从buffer2中分配内存
  }
分析下面的代码:

#include <iostream>
#include <new>
using namespace std;

const int N = 5;
const int BUF = 512;
char buffer[BUF];

int main()
{
	double *pd1, *pd2;
	int i;
	cout << "调用定位new运算符:\n";
	pd1 = new double[N];						//在堆(heap)中分配内存
	pd2 = new (buffer) double[N];				//在buffer中分配内存
	for (i = 0; i < N; i++)
		pd2[i] = pd1[i] = 1000 + 20.0 * i;
	cout << "存储地址:\n";
	cout << "	heap: " << pd1 << "; buffer: " << (void *)buffer << endl;//用(void *)对buffer进行强制类型转换,防止输出字符串,因为buffer是char类型的字符串数组
	for (i = 0; i < N; i++)
	{
		cout << pd1[i] << " @ " << &pd1[i] << "; "
			<< pd2[i] << " @ " << &pd2[i] << endl;
	}

	cout << "\n第二次调用定位new运算符:\n";
	double *pd3, *pd4;
	pd3 = new double[N];
	pd4 = new (buffer) double[N];
	for (i = 0; i < N; i++)
		pd4[i] = pd3[i] = 1000 + 40.0 * i;
	cout << "存储地址:\n";
	for (i = 0; i < N; i++)
		cout << pd3[i] << " @ " << &pd3[i] << "; "
		<< pd4[i] << " @ " << &pd4[i] << endl;

	cout << "\n第三次调用定位new运算符:\n";
	delete[] pd1;
	pd1 = new double[N];
	pd2 = new (buffer + N * sizeof(double)) double[N];//将从buffer开始算起偏移N * sizeof(double)的位置分配给pd2
	for (i = 0; i < N; i++)
		pd2[i] = pd1[i] = 1000 + 60.0 * i;
	cout << "存储地址:\n";
	for (i = 0; i < N; i++)
		cout << pd1[i] << " @ " << &pd1[i] << "; "
		<< pd2[i] << " @ " << &pd2[i] << endl;
	delete[] pd1;
	delete[] pd3;									//pd2和pd4使用的是buffer的静态内存,delete只能释放由new分配的堆内存,所以pd3和pd4不能用delete释放
	return 0;
}

运行结果如下图:
运行结果
  示例中没有使用delete释放使用定位new运算符分配的内存,实际是因为在这个示例中不能这样做。buffer的内存是静态内存,而delete只能用于常规new运算符分配的堆内存。另一方面,如果buffer是使用常规new运算符创建的,便可以使用常规delete运算符来释放整个内存块。
  定位new运算符的基本原理是返回传递给它的地址,并将其转换为void *,以便能够赋给各种指针类型。但这说的是默认定位new函数,C++允许程序员重载定位new函数。

三、名称空间

1.名称空间的特性

  下面使用了关键词namespace创建了两个名称空间:Jack和Jill。

namespace Jack
{
	double pail;
	void fetch();
	int pal;
	struct well {……};
}
namespace Jill
{
	double bucket(double n) {……}
	double fetch;
	int pal;
	struct Hill {……};
}

  名称空间是全局的,也可以位于另一个名称空间中,但不能位于代码块中。名称空间是开放的,即可以把名称加入到已有的名称空间中,例如,下面代码将goose加入到名称空间Jill中:
  namespace Jill
  {
    char * goose(const char *);
  }
  原来的名称空间Jack为函数fetch()提供了原型,可以在该文件后面(或者另一个文件中)再次使用名称空间Jack位其提供定义:

namespace Jack
{
	void fetch()
	{
		……
	}
}

  通过作用域解析运算符::,使用名称空间来限定改名称:
  Jack::pail = 12.34;
  Jill::Hill mole;
  Jack::fetch();

1.1using声明和using编译指令

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

char fetch;
int main()
{
	using Jill::fetch;	//使名称空间Jill中的fetch在main()中可用
	cin >> fetch;		//读入Jill::fetch的值
	cin >> ::fetch;		//读入全局变量fetch的值,即char fetch
}

  using 编译指令使整个名称空间中的名称都可用:
  using namespace Jack;

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

  如果某个名称已经在函数中声明了,则不能使用using声明导入相同的名称;使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名。

char fetch;
int main()
{
	using namespace Jill;
	double fetch;						//隐藏Jill::fetch
	cin >> fetch;						//读入局部变量fetch的值
	cin >> ::fetch;						//读入全局变量(char fetch)的值
	cin >> Jill::fetch;					//读入Jill::fetch的值
}

1.3名称空间的其他特性

  名称空间的嵌套:

namespace elements
{
	namespace fire
	{
		int flame;
		……
	}
	float water;
}

  flame指的是elements:🔥:flame。
  可以在名称空间中使用using编译指令和using声明:

namespace myth
{
	using Jill::fetch;
	using namespace elements;
}

  Jill::fetch现在位于名称空间myth中,要访问它可以这样:myth::fetch,它也位于Jill中,也可以这样使用:std::cin >> Jill::fetch.
  using namespace myth相当于:
  using namespace myth;
  using namespace elements;
  using namespace fire;
  因为fire位于elements中,elements位于myth中。
  可以给名称空间创建别名:

namespace my_very_favorite_things {……};
namespace mvft = my_very_favorite_things;
namespace MEF = myth::elements::fire;
using MEF::flame;

1.4未命名的名称空间

  可以创建没有名称的名称空间:

namespace				//没有名称
{
	int ice;
	int bandycoot;
}

  该名称空间不能在其他位置使用,只能在该文件中使用名称空间中的名称,这成为了链接性为内部的静态变量的替代品:

static int counts;							//静态变量,内部链接性
int main()
{
	……
}

  采用名称空间的方法如下:

namespace
{
	int counts;
}
int main()
{
	……
}

2.名称空间示例

namesp.h
#pragma once

#ifndef NAMESP_H_
#define NAMESP_H_

#include <string>

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);
}

#endif // !NAMESP_H_
namesp.cpp
#include <iostream>
#include "namesp.h"

namespace pers
{
	void getPerson(Person &rp)
	{
		std::cout << "输入fname:";
		std::cin >> rp.fname;
		std::cout << "输入lname:";
		std::cin >> rp.lname;
	}
	void showPerson(const Person &rp)
	{
		std::cout << rp.fname << "." << rp.lname;
	}
}

namespace debts
{
	void getDebt(Debt &rd)
	{
		getPerson(rd.name);
		std::cout << "输入amount:";
		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 sum = 0;
		for (int i = 0; i < n; i++)
		{
			sum += ar[i].amount;
		}
		return sum;
	}
}
main.cpp
#include <iostream>
#include "namesp.h"

int main()
{
	using std::cin;
	using std::cout;
	using std::endl;
	debts::Debt family[2];
	cout << "输入第一个成员的信息:\n";
	debts::getDebt(family[0]);
	cout << "\n输入第二个成员的信息:\n";
	debts::getDebt(family[1]);
	cout << "\n第一个成员:\n";
	debts::showDebt(family[0]);
	cout << "\n第二个成员:\n";
	debts::showDebt(family[1]);
	double sum;
	sum = debts::sumDebts(family, 2);
	cout << "总资产: $" << sum << endl;
	return 0;
}

总结

  以上就是本文的内容——C++的内存模型和名称空间,包括变量存储的持续性、作用域、链接性和名称空间的特性和使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值