内存模型和名称空间

单独编译

常规头文件中包含的内容:
函数原型
使用#define 或const定义的符号常量
结构声明 //不创建变量,只是在原代码文件中声明结构变量时,告诉编译器将如何创建结构变量。
类声明
模板声明
内联函数
“ ”的方式头文件,编译器将首先在当前的工作目录查找,然后去标准位置
<>的头文件,编译器将在主机系统的文件系统中查找。

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

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

作用域和链接

作用域描述了名称在文件的多大范围内可见。(函数中定义的变量,只能在函数中使用)
链接性描述了名称如何在不同单元(文件)间共享。链接性为外部的名称可在文件间共享,内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。
C++函数的作用域可以是整个类或者整个名称空间(包括全局的),但不能是局部的(因为不能在代码块内定义函数,这样函数没有任何意义,外界不能调用)

自动存储持续性

自动变量只在包含它们的函数或者代码块中可见。

int main()
{
	int mm = 10;
	cout << mm << endl;   //10
	{
		int mm = 56;
		cout << mm << endl;    // 56    栈所以屏蔽
	} 
	cout << mm << endl;     //10
}
  1. 自动变量初始化 int x =10; int x = 5*2;
  2. 栈。 留出一段内存,将其视为栈,以管理变量的增减。栈的默认长度取决于实现,编译器通常提供改变栈长度的选项。栈顶指针,指向下一个可用的存储单元。当函数被调用时,将自动变量压入栈中,函数结束时出栈,释放内存。值还是原来的,但是该段内存可用。
    使用栈传递参数
  3. 寄存器变量
    register int cout = 5;
    register最初由C语言引入,它建议编译器使用CPU寄存器来存储自动变量。旨在提高访问变量的速度。
    C++11后使用它的唯一原因,指出程序员想使用一个自动变量。这个变量的名称可能与外部的变量相同。(与以前的auto相同)

静态持续变量

C++为静态存储持续性变量提供了3种链接性:外部链接性(可在其它文件中访问),内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。这三种都在整个程序执行期间就存在(编译器分配固定的内存块来存储所有的静态变量),若没有初始化,则自动初始化为0;
外部链接性:代码块的外面声明
内部链接性:代码块的外面声明,并用static
无链接性:代码块内声明,并用static

int global = 1000; //其它文件也可以用 静态持续变量
static int one_file = 50; // 只有本文件可以用
int fun() 
{
	static int n = 6; //  只初始化一次 。函数结束了,n依然存在,下次调用还保持之前的数值
	int la = 4;
}
  1. 静态持续性,外部链接性
    又称全局变量。
    在每个使用外部变量的文件中,都必须声明它。单定义规则,变量只能定义一次.为满足这种规则,C++提供了两种变量声明,一种定义,分配存储空间,一种引用声明,不分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,且不进行初始化,否则声明为定义,导致分配存储空间。
    即在多个文件中使用全局变量,只需在一个文件中包含该变量的定义,其它使用该变量的文件使用extern声明它
file2.h
#pragma once
extern int gloal = 1000;  //加不加extern都可以
#include<iostream>
void f2()
{
	std::cout << "file2 : " << gloal << std::endl;
	int gloal = 111;
	std::cout << "file2 : " << gloal << std::endl;  //同名局部变量隐藏全局变量
	std::cout << "file2 : " << ::gloal << std::endl;  //使用::运算符使用全局
}
//file1.h
#pragma once
#include"file2.h"  //没有也能编译过去
extern int gloal;  // 不能 extern int gloal = 500,会重定义时
#include<iostream>
void f1()
{
	std::cout << "file1 : " << gloal << std::endl;
	gloal = 500;
}
int main()
{
	f2();  //1000
	f1();  // 1000
	f2();  //500
}

注意:程序越能避免对数据进行不必要的访问,越能保持数据的完整性。通常应该使用局部。全局也有好处,可以让多个函数使用同一个数据块,例如每年的四季这种常量数据,可以使用const来防止数据被修改。
2. 静态持续性,内部链接性

//test.cpp
#include<iostream>
extern int tom;
static int dick = 10;
int harry = 200;
void remote_access()
{
	using namespace std;
	cout << "remote_access :\n";
	cout << &tom << " = &tom\n";
	cout << &dick << " = &dick\n";
	cout << &harry << " = &harry\n";
}
#include <iostream>
//不用包括文件也可以编译通过,编译器已经处理了 而且全局变量
using namespace std;
int tom = 3;
int dick = 30;
static int harry = 200;
void remote_access();
int main()
{
	cout << " main remote_access :\n";
	cout << &tom << " = &tom\n";
	cout << &dick << " = &dick\n";
	cout << &harry << " = &harry\n";
	remote_access();
}

在这里插入图片描述
由图可知:两个文件使用同一个tom变量,但使用了不同的dick和harry变量。

  1. 静态存储持续性,无链接性
using namespace std;
void fun()
{
	static int num = 2;  //只初始化一次,且值一直存在
	cout << num << endl;
	num++;
}
int main()
{
	fun();   //2
	fun();   //3
	fun();   //4
}

  1. 说明符和限定符
    cv限定符(存储说明符)
    C++11前,auto为自动变量,C++11后,auto用于自动类型推断。
    (1)cv限定符
    const 和volatile(易挥发的)
    volatile禁止编译器优化,若同一个值出现多次,编译器将其放入缓存,默认其不会改变。
    (2)mutable 即使结构(或类)变量为const,其某个成员也可被修改。
    struct Data
    {
    int a;
    mutable int b;
    }
    const Data d={1,4};
    d.a++; //error
    d.b++; //可以
    (3)在谈const
    在C++中,const限定符对默认存储类型稍有影响。在默认情况下,全局变量的链接性为外部,但const 全局变量的链接性为内部。在C++看来,全局const定义,就像使用了static说明符一样。
    const int a = 10; ==>static const int a = 10;
    假如将一组常量定义放在头文件中,并在同一个程序的多个文件使用该头文件。那么预处理器将头文件的内容包含到每个源文件中,所有源文件都将包含类似 const int a = 10;这样的定义。若const 全局变量的链接性为外部,则根据单定义原则,这将出错,即只能有一个文件可以包含前面的声明,而其它文件必须使用extern来提供引用声明。另外只有未使用extern关键字的声明才能进行初始化。
    内部链接性意味着每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的,这就是能将常量定义放在头文件中的原因。这样,只要在源代码文件中包括同一个头文件,则它们将获得同一组常量。
    若希望某个常量的链接性为外部,则可以使用extern关键字覆盖默认的内部链接性。extern const int a = 10; 在这种情况下,必须在所有使用该常量的文件中使用关键字extern来声明它。这与常规的外部变量不同,定义常规外部变量时,不必使用extern,但在使用该变量的其它文件中必须用extern。然而,记住,单个const 在多个文件之间共享,因此只有一个文件可对其进行初始化。
    在函数或代码块中声明const 时,其作用域为代码块,即仅当程序执行该代码块中的代码时,该常量才可用的。意味着在函数或者代码块中创建常量时,不必担心其名称与其它地方定义的常量冲突。

函数和链接性

默认情况下,函数链接性为外部的,即可以在文件之间共享。
可以在函数原型使用关键字extern指出函数是在另一个文件中定义的,这是可选的。还可以使用关键字static(定义和声明都需要)将函数声明为内部的,使之只能在一个文件内使用。意味着该函数只能在该文件内可见,其它文件中可以定义同名的函数。
对于每个非内联的函数,程序只能包含一个定义。对于链接性为外部的函数来说,意味着在多文件程序中,只能有一个文件包含该函数的定义,但使用该函数的每个文件都应该包括其函数原型。
C++在哪里找函数定义:若函数为静态,则编译器只在该文件中查找;否则在所有程序文件中查找。若找到两个定义,将报错。因为每个外部函数只能有一个定义。如果在程序文件中找不到,则在库中搜索,意味着如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数(但是C++保留了标准库函数名称,即程序员不能使用它们)。

语言链接性

void fun(int i)
C语言链接性 : c编译器将函数翻译为 _fun
C++: _fun_i
若在C++使用C版本
extern “c” void fun(int i);
void fun(int i); 默认C++
extern “c++” void fun(int i); 显示指出C++版本

存储方案和动态内存分配

通常编译器使用三块独立的内存:一块静态变量(可能在细分),一块用于自动变量,一块用于动态存储。

  1. 使用new运算符初始化
    c98:若为基本数据类型
    int *p = new int(6); *p = 6;
    若初始化常规结构或数组,使用大括号的列表初始化,需要编译器支持C++11.
    stu *ps = new stu{1,2,3};
    int *a = new int【4】{1,2,3};
    C++11 中还可以用于单值初始化将初始化列表
    int *p = new int{6};
  2. new 失败。 最初new失败返回空指针,但现在将引发异常std::bad_alloc
  3. new:运算符,函数和替换函数
    运算符new 和new【】分别调用如下函数
    void * operator new(std::size_t);
    void * operator new ;
    这些函数被称为分配函数,它们位于全局名称空间中,同样也有delete 和delete【】调用的释放函数
    int *p = new int;将转为 int *p = new(sizeof(int));
    int *p = new int[10];将转为 int p = new(10sizeof(int));
    c++将这些函数称为可替换。意味着如果你有足够的知识,可为new 和delete提供替换函数,并根据需要对其进行定制。例如可定义作用域为类的替换函数,并对其进行定制,以满足该类的内存分配要求。在代码中使用new运算符,但将调用自己定义的new函数。
  4. 定位new运算符 头文件new。指定在哪里分配内存
#include <iostream>
#include<string.h>
#include<new>
using namespace std;
const int Buf = 512;
const int N = 3;
char buff[Buf];
int main()
{
	double* pd1, * pd2;
	int i = 0;
	pd1 = new double[N];
	pd2 = new(buff)double[N];
	for (i = 0; i < N; i++)
	{
		pd1[i] = pd2[i] = 100.0 + 2.0 * i;
	}
	cout << "memeory address:\n" << " heap: " << pd1 << " static :" << (void*)buff << endl;
	cout << "memeory content:\n";
	for (i = 0; i < N; i++)
	{
		cout << pd1[i] << " at " << &pd1[i] << endl;
		cout << pd2[i] << " at " << &pd2[i] << endl;
	}
	cout << "=======================================\n";
	double* pd3, * pd4;
	pd3 = new double[N];
	pd4 = new(buff)double[N];
	for (i = 0; i < N; i++)
	{
		pd3[i] = pd4[i] = 100.0 + 4.0 * i;
	}
	cout << "memeory content:\n";
	for (i = 0; i < N; i++)
	{
		cout << pd3[i] << " at " << &pd3[i] << endl;
		cout << pd4[i] << " at " << &pd4[i] << endl;
	}
	cout << "=======================================\n";
	delete[]pd1;
	pd1 = new double[N];
	pd2 = new (buff + N * sizeof(double))double[N];
	for (i = 0; i < N; i++)
	{
		pd1[i] = pd2[i] = 100.0 + 6.0 * i;
	}
	for (i = 0; i < N; i++)
	{
		cout << pd1[i] << " at " << &pd1[i] << endl;
		cout << pd2[i] << " at " << &pd2[i] << endl;
	}
	cout << "memeory content:\n";
	for (i = 0; i < N; i++)
	{
		cout << pd1[i] << " at " << &pd1[i] << endl;
		cout << pd2[i] << " at " << &pd2[i] << endl;
	}
	delete[] pd1;
	delete[] pd3;
}

原理:定位new运算符,只是返回传递给它的地址,并将其强制转换为void* 以便能够赋给任何指针类型。(默认定位new运算符这样),允许重定义new。
定位new运算符不可替换,但可以重载。至少接受两个参数,其中一个总是size_t,指定了请求的字节数。

名称空间

通过定义一种新的声明区域来创建命名的名称空间,目的:提供一个声明名称的区域。让一个名称空间中的名称不会与另一个名称空间冲突。
名称空间可以是全局的,也可以位于另一个名称空间中(嵌套),但不能位于代码块中。因此默认情况下,名称空间中声明的名称的链接性为外部的。
除了用户定义的名称空间,还存在另一个名称空间——全局名称空间,对应于文件级别的声明区域。因此全局变量被描述为位于全局名称空间中。

namespace Jack {
	void fun() { cout << "Jack::fun\n"; }
	int a = 6;
}
namespace Jack {   //名称空间是开放的,可以加
	int b = 9; 
}
namespace Mary {
	void fun() { cout << "Mary::fun\n"; }
	int a = 1;
}
int main()
{
	Jack::fun();
	Mary::fun();
	cout << Jack::b << endl;
	using Jack::fun;
	fun();   //jack
}
  1. using 声明和using 编译指令
    using 声明 使特定的标识符可用
    using Jack::fun;
    fun(); //jack 使用该声明后,fun代替Jack::fun;
    using 编译指令使整个名称空间可用。
namespace Jack {
	void fun() { cout << "Jack::fun\n"; }
	int a = 6;
}
int a = 10;
int main()
{
	using Jack::a;
	cout << a << endl;  //6
	cout << ::a << endl; //10
}

using声明将名称添加到局部声明的区域中,因此避免了将另一个参数也名为a。(新定义a会报错),此外和其它局部变量一样,a也将覆盖同名的局部变量。
在函数外面使用using 声明,将把名称添加到全局名称空间中
using声明使一个名称可用,using 编译指令使所有名称都可用。
using namespace Jack;
2. using 声明和using 编译指令比较。使用using 编译指令导入一个名称空间中所有的名称与使用多个using 声明是不一样的,更像是使用了大量的作用域解析运算符。using 声明好像声明了相应的名称一样。如果某个名称已经在函数中声明了,则不能使用using 声明导入相同的名称。但是使用using 编译指令,将进行名称解析,就像在包含using声明和名称空间本身的最小声明区域中声明了名称一样。

namespace Jack {
	void fun() { cout << "Jack::fun\n"; }
	int a = 6;
	struct Hill {};
}
int a = 10;
int main()
{
	using namespace Jack;
	Hill h;   //Jack:: Hill
	fun();//Jack:: fun
	int a = 11; //隐藏Jack 和全局的
	cout << a << endl;  //6
	cout << ::a << endl; //10
}
int foo() 
{
	//Hill ts; // error
	Jack::Hill aaa;
}

一般来说使用using声明比using 编译指令更安全,因为只导入指定的名称,若与局部冲突了,则编译器会报错,但using 编译指令,局部会覆盖名称空间。此外由于名称空间的开放性,名称可能分散在各个地方。
注意:将using namespace std;放在函数中,将在函数中可用。放在外面,则std所有内容都导出到全局名称空间中。

  1. 名称空间的其它特性
    可以将名称空间进行嵌套
    可以在名称空间中使用using 编译指令和using 声明
    可以给名称空间创建别名
    namespace Jack {
    void fun() { cout << “Jack::fun\n”; }
    int a = 6;
    struct Hill {};
    }
    namespace ww = Jack;

  2. 未命名的名称空间
    namespace { int a = 4;} =》static int a = 4;
    类似于一个文件内的全局变量

使用示例

//namesp.h
#pragma once
#include<string>
namespace pers {
	struct  Person
	{
		std::string fname;
		std::string lname;
	};

	void getPerson(Person &p);
	void showPerson(const Person& p);
}
namespace debts {
	using namespace pers;  //让pers在debts名称空间中可用
	struct  Debt
	{
		Person p;
		double amount;
	};
	void getDebt(Debt& d);
	void showDebt(const Debt& d);
	double sumDebts(const Debt ar[], int n);
}
//namesp.cpp
#include"namesp.h"   //包含了名称空间
#include<iostream>
//在cpp文件中定义名称空间的函数,由于开放性,使其可用
namespace pers {
	using std::cout;
	using std::cin;
	using std::endl;
	void getPerson(Person& p) 
	{
		cout << "Enter first name \n";
		cin >> p.fname;
		cout << "Enter last name \n";
		cin >> p.lname;
	}
	void showPerson(const Person& p)
	{
		cout << p.fname << " : " << p.lname << endl;
	}
}

namespace debts {
	void getDebt(Debt& d)
	{
		getPerson(d.p);
		std::cout << "enter debt\n";
		std::cin >> d.amount;
	}
	void showDebt(const Debt& d)
	{
		showPerson(d.p);
		std::cout << "debt : " << d.amount;
	}
	double sumDebts(const Debt ar[], int n)
	{
		double t = 0;
		for (int i=0;i<n;i++)
			t += ar[i].amount;
		return t;
	}
}

名称空间以及其前途

  1. 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量和静态全局变量
  2. 如果开发一个函数库或者类库,将其放在一个名称空间中。
  3. 仅将编译指令using作为一种将旧代码转换为使用名称空间的权益之计。
  4. 不要在头文件中使用using编译指令。首先这样掩盖了要让哪些名称可用。另外,包含头文件的顺序可能影响程序的说明。如果非要使用,则应该将其放在所有预处理器编译指令#include之后
  5. 导入名称时,首选使用作用域解析运算符或using声明的方法
  6. 对于using声明,首选将其作用域设置为局部,而不是全局。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值