内存模型和名称空间

本文探索的主题:
单独编译,大型程序分为头文件、函数定义、函数使用三大功能模块
探讨了自动变量、静态变量、动态变量的存储持续性(潜在作用域)、链接性(多个文件是否共享名称)、作用域(可见)
创建名称空间、使用名称空间中的名称(限定符、using声明、using编译指令)

单独编译:
大型程序通过由多个源代码文件组成,每个源代码文件单独编译然后链接成为可执行程序。编译器不但能编译程序,也能管理链接器。
大型程序组织策略:
头文件:多个源代码文件可能都需要使用的结构,以及函数原型,便于修改和管理,只需要改动头文件即可
源代码文件:与头文件中结构相关的函数定义
源代码文件:调用与结构相关的函数
或是:
头文件:多个源代码文件都需要使用到的类
源代码文件:使用类

头文件能包含哪些内容?
类声明、结构声明、函数原型、模板函数声明、内联函数、使用#define或const修饰的符号常量;坚决不能包含函数定义、类变量、结构变量,否则将出现同一个程序出现多个相同函数的定义或是多次尝试定义相同的变量。
包含头文件的写法:
<**.h> 编译器将优先从标准头文件中查找,适用于使用标准库提供的头文件
<".h"> 编译器将优先从当前项目文件中查找,适用于自定义的头文件,查不到时编译器将尝试从标准库文件中查找

编译程序过程
将程序所需的源代码放入到同一个项目目录下,编译该项目,将每个文件单独编译为目标代码文件,编译器会根据#include命令自动添加头文件,链接器将启动代码、目标代码文件、库代码合并生成可执行文件。

一个文件仅能包含一个头文件一次,但头文件可能存在包含关系,即头文件中又包含另一个头文件,那么该如何解决呢?
提供预处理器编译指令#ifndef. …#endif,自己在定义头文件时使用该语法,将头文件内容写在ifndef…define…endif中,具体使用如下:
在这里插入图片描述
之前没有包含 coordin.h头文件,则将其包含进来;否则,跳过该段代码。由此可避免重复引用相同的声明。

#ifndef COORDIN_H_
#define COORDIN_H_


struct rect
{
	int x;
	int y;
};

struct polar
{
	double radius;
	double angle;

};

void rect_to_polar(const rect &,polar &);
void show(polar &);


#endif

举个小例子,将之前rect转化为polar的程序写成多个文件的结构,调试时一开始使用了预编译头,程序编译出现了error,后来不使用预编译头即可编译成功。有一个疑问:与名称空间有关,每个文件都得相同的头文件,但明明头文件在最后被链接起来,编译过程理解不清。

2.cpp
#include"coordin.h"
#include<iostream>

int main()
{
	rect one = { 3,4 };
	polar two;
	rect_to_polar(one, two);
	show(two);
	std::cin.get();
	std::cin.get();
    return 0;
}
1.cpp
#include"coordin.h"
#include<iostream>
#include<cmath>

using namespace std;

void rect_to_polar(const rect &a, polar &b)
{
	b.radius=sqrt(a.x*a.x + a.y*a.y);
	b.angle = atan2(a.y, a.x)*ANG;
}
void show(polar &a)
{
	cout << a.angle << " " << a.radius << endl;
}

coordin.h
#ifndef COORDIN_H_
#define COORDIN_H_


struct rect
{
	int x;
	int y;
};

struct polar
{
	double radius;
	double angle;

};

void rect_to_polar(const rect &,polar &);
void show(polar &);

const double ANG = 57.3;
#endif

存储持续性、作用域、链接性
作用域和链接:
作用域描述了名称在文件中多大的范围内可见
链接性描述了名称如何在不同的单元间共享,链接性为外部的名称可在不同的文件间共享,链接性为内部的名称只可在一个文件中共享。自动变量没有链接性,只能在函数内部共享。
函数作用域是全局或是属于类的,不能使局部的,因为函数内部不能定义函数。
作用域分为局部和全局(文件)

存储数据不同的方案:
1.自动存储持续性:在函数内定义的变量和参数随着函数的调用被创建、调用结束后被释放。
分析:
变量持续性和作用域被限制在代码块内,代码块嵌套代码块,在外部和内部声明两个同名的变量,内部代码块中的变量将覆盖外部同名变量。
在这里插入图片描述

//自动变量作用域和时效性

#include<iostream>
using namespace std;

void test(int a);

int main()
{
	int x;
	cin >> x;
	cout << &x << endl;
	test(x);//值传递 调用test函数后,x依旧存在于内存中,但不可见,函数参数和内部声明变量可见
	cout << &x << endl;//函数调用结束后,函数内部声明变量和参数被释放,但x可见
	cin.get();
	cin.get();
    return 0;
}

void test(int a)//a的作用域和时效性都是在整个函数定义内的
{
	int x = 3;
	cout << &x << endl;
	cout << a << endl;
	{
		int a = 0;
		cout << a << endl;test函数内外部定义的变量a 存在但不可见,被新定义的a所覆盖
		cout << &x << endl;//test函数内外部定义的变量x 存在且可见的
	}
	cout << &x<<endl;
	cout << a << endl;
	
}

随着函数调用自动变量开始增加,函数结束自动变量减少,如何在内存中表示这种趋势呢?
提前预留出一片内存空间,以栈方式管理这片内存(不断变化栈顶指针),刚开始调用函数时,将实参复制到栈中并更改栈顶指针,随着作用域的结束,更改栈顶指针。可以看出,自动变量没有从栈中被删除,只是由于栈顶指针的存在,使得其无效,可以被之后的值替换。
在这里插入图片描述

2.静态存储持续性:函数外定义的变量和用static修饰的变量,在程序整个执行过程中都存在,因而内存管理更为简单,分配固定的内存空间来存储变量,无论是否有显示的初始化,都先进行零初始化。
初始化方式:零初始化、常量表达式、动态初始化(比如,在编译的时候,使用了标准库文件中的函数,这得在链接生成可执行文件后才得以计算出值)。
时效性:在整个程序执行期间内都是存在的。
根据链接性,可以将静态变量分为三种:
链接性为外部的静态变量:声明在函数定义之外,不用static修饰,程序所有的文件可访问该变量。
链接性为内部的静态变量:声明在函数定义之外,用static修饰,只有包含定义变量的文件才可访问该变量。
无链接性的静态变量:声明在函数定义内,用static修饰,只能在函数代码块内被访问。

int external_var = 5;//链接性为外部的静态变量 声明在函数外部就已经满足静态持续性
static int  internal_var;//链接性为内部的静态变量  可在main test中被访问 用static区别链接性
void test(int a)//a的作用域和时效性都是在整个函数定义内的
{
	static int y;//即便test没有被调用,当程序被执行时,就被存入到固定内存了 用static标识存储的持续性
}

在这里插入图片描述
外部链接性:

链接性为外部的变量成为外部变量,也称为全局变量。
单定义规则:在每个使用外部变量的文件中,必须要事先声明外部变量才能使用其他文件定义的外部变量;且变量只能定义一次。为满足这种要求,为外部变量提供两种变量声语法:定义外部变量,分配存储空间;声明外部变量,引用已经定义过的外部变量。
引用(声明)外部变量:用extern标识同时不可以赋初始值;否则就是定义新的外部变量。

在这里插入图片描述
在这里插入图片描述
自动变量与外部变量同名时,自动变量将覆盖全局变量,但要是希望依旧使用全局变量,可使用修饰符::指明使用全局变量。
全局变量不安全,任何函数都能使用或是修改;但是全局变量适用在存储程序需要多次使用到的常量。

//   程序分为两个部分;一个用于定义函数,一个用于调用函数;测试两个文件对于外部变量的使用
1.cpp
#include<iostream>
using namespace std;

void add(int value);
void testlocal();

int external_var = 2;


int main()
{
	add(6);
	cout << external_var << endl;
	testlocal();
	cin.get();
    return 0;
}

2.cpp
#include<iostream>
using namespace std;

extern int external_var;

void add(int value);
void testlocal();


void add(int value)
{
	cout << external_var << endl;
	external_var += value;
	
}
void testlocal()
{
	int external_var = 19;
	cout << external_var << endl;
	cout << ::external_var << endl;
}

内部链接
多个文件,一个文件定义了外部变量,另一个文件希望定义一个与该外部变量同名的内部链接静态变量,则在该文件中使用static来定义变量,且不要声明同名外部变量。

在这里插入图片描述

1.cpp
#include<iostream>
using namespace std;

int x = 3;//在程序1中引用该外部变量,同一个内存块
int y = 4;//外部变量,但1中没有引用,而是重定义了一个同名的内部链接变量
static int z = 8;//定义了内部链接变量 在程序2中定义了同名的外部链接变量

void test();

int main()
{
	cout << &x << endl;
	cout << &y << endl;
	cout << &z << endl;
	test();
	cin.get();
    return 0;
}


2.cpp
#include<iostream>
using namespace std;


extern int x;
static int y = 19;
int z = 20;
void test()
{
	cout << &x << endl;
	cout << &y << endl;
	cout << &z << endl;
}

无链接性局部变量:
虽然是局部变量,但是程序开始执行时,便在固定的内存块内为其分配了空间,哪怕是多次调用,也只进行一次初始化,该静态局部变量的值保持为上次调用之后的值。这种变量在多次循环操作时,可以用来记录一些值。

//不断输入字符串,以空行为结束输入;如果输入的行是不完整行,则删除不完整行的后续字符;使用静态局部变量来计算所有输入字符串的字符个数

#include "stdafx.h"

#include<iostream>
using namespace std;

void strcount(char * str);
const int SIZE = 10;

int main()
{
	char string[SIZE];
	cin.get(string, SIZE);
	char next;
	while (cin)//输入时空行,返回值为false
	{
		cin.get(next);//cin.get(string, SIZE)读入字符到时,如遇到换行停止;或是读到指定的个数停止;但是换行符不读出来放在字符流中,取出读完后的第一个字符,看是哪一种情况
		while (next != '\n')//字符串没读完
			cin.get(next);//删除输入流中未读取完的字符 如果不删除 将先读入这些字符
		strcount(string);
		cin.get(string, SIZE);
		
	}
	
    return 0;
}

void strcount(char * str)
{
	int count = 0;
	static int total = 0;//初始化只被执行一次
	while (*str++)
		count++;
	total += count;
	cout << total << endl;
}

const修饰符:
const修饰表示变量内容不能再更改是常量!
使用const修饰、在函数外部定义的变量是内部链接变量。
因而,可在头文件中定义,在不同的文件中通过包含头文件来获取常量。

函数链接性默认为外部的,文件间可互相调用函数,与单定义规则类似,只可以在一个文件中进行定义,在其余文件中调用该外部函数前需要要加函数的原型声明。如希望更改函数的链接性为内部,仅在当前文件中被访问,可使用static修饰函数定义和原型;此外,可在其他文件中定义同名同参的函数,同时静态函数将覆盖同名的外部函数。

调用函数时,需要找到函数的定义,按何种策略查找函数定义呢?
先在该文件中看函数原型,若是静态函数,则在该文件中查找函数定义;否则,在该程序的所有文件中查找被调用函数定义;否则,在库文件中查找被调用函数定义

3.线程存储持续性:变量的存在和释放与所属线程相同

4.动态(自由)存储持续性:new分配的内存空间将一直存在除非用delete释放或是程序终止。分配的空间位于堆中。

与自动变量和静态变量不同,动态变量不受作用域和链接性的影响,内存空间分配和释放就是由new和delete确定的,但是指向由new创建的内存块的指针受作用域和链接性控制的。
在这里插入图片描述
接下来主要讲讲new运算符的一些用法!!!
new运算符分配空间并初始化语法:
基本类型变量的初始化可在类型后加(初值):
在这里插入图片描述
数组和结构等复杂类型,在类型后用列表,进行列表初始化:
在这里插入图片描述

使用new运算符,实际调用的函数如下,也称为全局的分配函数:
在这里插入图片描述
在这里插入图片描述

由分配函数形式可知,函数的返回值是第一个元素的地址,类型为void * 指向任意类型的指针

定位new使用:
与常规new运算符相比,常规new是在堆中随机找足够的内存来分配,定位new是在指定的位置找足够的内存来分配。使用指定的new运算符需要包含头文件new,并在使用new运算符时提供具体的地址。
在这里插入图片描述
定位new运算符的函数定义,两个参数,一个是分配空间的大小,一个是给定的位置:
在这里插入图片描述
比较常规new和定位new差异:
1.两次定位new给定的位置相同,则第二次分配的空间依旧从指定的位置开始,无论是否存有数据;而常规new不同,只要分配的空间没有被释放,该空间就不会再被利用。
2.定位new所给定的位置若不是由堆所控制的,就不能使用delete释放该片空间

//new运算符的用法

#include "stdafx.h"
#include<new>
#include<iostream>
const int SIZE = 100;
const int N = 10;
char name[SIZE];

using namespace std;


int main()
{
	double *p = new double[N];//常规new
	double *p1 = new (name)double[N];//定位new 且使用静态空间来分配空间 就不能使用delete来释放空间
	cout << p << endl;
	cout << (void *)name << endl;
	for (int i = 0; i < N; i++)
		cout << &p1[i] << endl;
	delete[] p;
	double *p2 = new(name) double[N];//p2 与p1 分配的空间相同,不管该片空间是否有数据值
	for (int i = 0; i < N; i++)
		cout << &p2[i] << endl;
	p = new double[N];//删除了之前的空间后再次分配空间有可能使用被删除的空间
	cout << p << endl;
	delete[]p;
	cin.get();
    return 0;
}

名称空间
名称可以是变量、函数、结构、结构中的成员、类中成员、类中函数等。项目越大,越有可能产生名称冲突。为了解决这个问题,c++提供了名称空间工具,用来管理名称的作用域。
比较原始的名称空间属性:
声明区域,名称可声明的区域。对于在代码块内声明变量,其声明区域是代码块;对于在函数外部定义的变量,其声明域是整个文件。
潜在作用域,名称从声明点之后到其声明区域的结束。通常声明域大于潜在作用域。
作用域,在其潜在作用域里可见的部分称为作用域。这是由于局部变量存在,便出现了作用域和潜在作用域的差别。
在这里插入图片描述
在这里插入图片描述

新的名称空间特性:

定义一个新的声明区域来创建命名的名称空间。提供一个名称的区域,一个名称空间中的名称不会与另一个名称空间中的相同名称冲突,同时允许程序的其他部分使用某一个名称空间的名称。在没有引入名称空间之前,视为全局名称空间,对应的是文件级的声明区域。
名称空间可分为:用户定义的名称空间和全局名称空间。
创建名称空间:namepace{变量定义;函数声明;函数定义}
在这里插入图片描述名称空间可以是全局的,也可是嵌套定义在另一个名称空间中,但不可在代码块内定义名称空间;默认情况下,名称空间中的名称具有外部链接性。
名称空间是开放的,可随时向其中加入新的名称,常用的是向一个已有的名称空间中加入函数定义。
在这里插入图片描述

如何访问名称空间中的名称?
1.限定符::和具体的名称空间的组合。书写起来比较麻烦,每次都要加上名称空间
在这里插入图片描述
2.using声明
使得特定的标识符可用,原理是将限定的名称加入到所属的声明区域(相当于在所属的区域内添加定义,因此如果在所属区域内再次定义,将出现error),之后便可直接使用标识符,而不用加上名称空间。
在这里插入图片描述
3.using编译指令
使得名称空间中的所有名称都可用,using编译指令与using声明不同,不理解为定义多个名称,而是导入时将其视为定义在函数之外的全局名称(但是在其他的函数中不能不加限定符来使用,但作用域在函数内部),因而在代码块内存在同名的局部变量定义,则该局部变量会覆盖名称空间中的同名名称。
在这里插入图片描述
#include<标准头文件>将头文件加入到名称空间std中,而在c语言中,没有名称空间这一概念,直接将名称加入到全局,而不是std。

名称空间的其他属性:
1.可嵌套
在这里插入图片描述
2.写可使用using声明或是using编译指令,而不用加限定符,目的是包含已有的名称。
在这里插入图片描述
在名称空间中使用using编译指令,当以using编译指令时,由于using编译指令据有传递性,将会同时包含myth和elements中的所有名称。
在这里插入图片描述
为名称空间创建别名,比较适用在多层嵌套,希望使用某一层名称空间中的名称,此时可先为具体某一层的名称空间设定别名,再利用该别名访问名称。
在这里插入图片描述
在这里插入图片描述

未命名的名称空间:
名称空间的潜在作用域时定义之后到声明域结束之前,没有名称,故不可使用using声明或是using编译指令。名称具有内部链接性,无法在其他文件中使用该名称空间的名称,对于内部全局变量很适合放在这样的名称空间中,此时不用加static修饰。
在这里插入图片描述`//两个名称空间,一个包含另一个,表明了using传递性,同时包含了名称空间pers中的所有名称
1.h
#include

namespace pers
{
struct Person
{
std::string fname;
std::string lname;
};
void getPerson(Person &);
void show(Person &);
}

namespace debt
{
const int SIZE = 2;
using namespace pers;
struct Debt
{
Person p;
double money;
};
void showDebt(Debt &);
void getDebt(Debt &);
double sum(Debt[],int );
}

2.cpp
//头文件中函数定义,充分表明了名称空间的开放性
#include"1.h"
#include
namespace pers
{
void getPerson(Person &p)
{
std::cin >> p.fname;
std::cin >> p.lname;
}
void show(Person &p)
{
std::cout << p.fname << " " << p.lname << std::endl;

}

}

namespace debt
{
using namespace std;
void showDebt(Debt &one)
{
show(one.p);
cout << one.money << endl;

}
void getDebt(Debt & one)
{
	getPerson(one.p);
	cin >> one.money;

}
double sum(Debt ones[],int n)
{
	double total=0;
	for (int i = 0; i < n; i++)
		total += ones[i].money;
	return total;
}

}

3.cpp
#include"1.h"
#include
void other();
void another();

//使用、调用名称空间中的名称
int main()
{
using debt::Debt;
Debt one = { {“li”,“xin”},2222 };
debt::showDebt(one);
other();
another();
std::cin.get();
std::cin.get();
return 0;
}

void other()
{
using namespace debt;
Debt one;
getDebt(one);
showDebt(one);
Person two;
getPerson(two);
show(two);
Debt many[SIZE];
for (int i = 0; i < SIZE; i++)
getDebt(many[i]);
std::cout<<sum(many,SIZE)<<std::endl;
}
void another()
{
using pers::Person;
Person one;
using pers::getPerson;
getPerson(one);
using pers::show;
show(one);

}`

使用名称空间的指导性观念:
1.将外部全局变量写入到名称空间中
2.将内部全局变量写入到无名字的名称空间中
3.开发函数库或类库时,将其写入std命名空间中
4.导入名称时,根据名称使用次数,选择using声明或是限定符表示
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值