《C++ primer plus》学习笔记——第九章内存模型和名称
文章目录
一、单独编译
(1)C++为在内存中存储数据方面提供了多种选择。
可以选择数据保留在内存中的时间长度(存储持续性)以及程序的哪一部分可以访问数据等等。
(2)C++名称空间是另一种控制访问权的方式。
(3)通常,大型程序都由多个源代码文件组成,这些文件可能共享一些数据。
这样的数据涉及到程序文件的单独编译。
1.单独编译
(1)和C语言一样,C++也允许甚至鼓励程序员将组件函数放在独立的文件中。
甚至也可以单独地编译这些文件,然后将他们链接成可执行的程序。
(2)通常,C++编译器既编译程序,也管理链接器。如果只修改了一个文件,则可以只重新编译该文件,然后将它与其它文件的编译版本链接。
(3)举一个eg,从中了解一下编译的细节:
在第七章中的<2.另一个处理结构的函数示例(值传递)>中,
解决办法如下:
所以,最终的结果是:
将原程序分为了三部分:
(a)头文件:包含结构声明和使用这些结构的函数原型
(b)源代码文件:包含与结构有关的函数的代码
(c)源代码文件:包含调用与结构相关的函数代码
(4)请不要将函数定义或者变量声明放到头文件中。 如果同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则,这将出错。
头文件中常常包含的内容如下:
(5)#include“coordin.h”与#include<coordin.h>的区别为:
注意:只需将源代码文件加如到项目中,而不用加如到头文件。
这是因为#include指令管理头文件,另外,不要使用#include来包含.cpp的源代码文件,这样会导致多重声明。
(6)在UNIX系统中编译由多个文件组成的C++程序的流程是:
(7)对于头文件的管理而言,
(8)最终的程序代码如下:
在coordin.h的头文件中的代码如下所示:
// coordin.h -- structure templates and function prototypes
// structure templates
#ifndef COORDIN_H_
#define COORDIN_H_
struct polar
{
double distance; // distance from origin
double angle; // direction from origin
};
struct rect
{
double x; // horizontal distance from origin
double y; // vertical distance from origin
};
// prototypes
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif
在file1.cpp的头文件中的代码如下所示:
// file1.cpp -- example of a three-file program
#include <iostream>
#include "coordin.h" // structure templates, function prototypes
using namespace std;
int main()
{
rect rplace;
polar pplace;
cout << "Enter the x and y values: ";
while (cin >> rplace.x >> rplace.y) // slick use of cin
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Bye!\n";
// keep window open in MSVC++
cin.clear();
while (cin.get() != '\n')
continue;
cin.get();
return 0;
}
在file2.cpp的头文件中的代码如下所示:
// file2.cpp -- contains functions called in file1.cpp
#include <iostream>
#include <cmath>
#include "coordin.h" // structure templates, function prototypes
// convert rectangular to polar coordinates
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; // returns a polar structure
}
// 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";
}
说明:
(a)注意一下,下面的C++专属控制台暂停代码的写法:
如果, 代码写成下面:
说明:while里面的语句只要不换行,一直可以cin.get(),如果换行了,就跳出while
则:
如果:
说明:如果while中的代码一直是换行(只检测换行符),那么就一直cin.get(),如果输入数字,再按下换行,就会跳出
则:
(b)
(9)C++多个库的链接
二、存储持续性、作用域和链接性
(1)存储类别如何影响信息在文件间的共享:
C++使用三种(C++11是四种)不同的方案来存储数据。
这些方案的区别在于:保留在内存中的时间。
(a)局部变量
(b)静态变量
(c)线性变量:使用关键字threa_local
(d)动态内存
1.作用域和链接
(1)作用域scope的作用:
(2)链接性linkage的作用:
(3)C++变量的作用域:
**局部变量的作用域:**只在定义它的代码块中可用;而代码块是由花括号括起来的一系列语句;
全局变量的作用域: 在定义位置到文件结尾之间都可用;
自动变量的作用域为局部的;
静态变量的作用域是全局还是局部的取决于它是被如何定义的。
函数原型的作用域function prototype scope以及在类中声明的成员的作用域以及在名称空间中声明的变量的作用域
C++函数的作用域
2.自动存储持续性
(1)在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。
eg1:
eg2:
eg2接着的话,
具体看下图就明白了:
(2)eg
#include<stdio.h>
#include<stdlib.h>
// autoscp.cpp -- illustrating scope of automatic variables
#include <iostream>
void oil(int x);
int main()
{
using namespace std;
int texas = 31;
int year = 2011;
cout << "In main(), texas = " << texas << ", &texas = ";
cout << &texas << endl;
cout << "In main(), year = " << year << ", &year = ";
cout << &year << endl;
oil(texas);
cout << "In main(), texas = " << texas << ", &texas = ";
cout << &texas << endl;
cout << "In main(), year = " << year << ", &year = ";
cout << &year << endl;
// cin.get();
system("pause");
return 0;
}
void oil(int x)
{
using namespace std;
int texas = 5;
cout << "In oil(), texas = " << texas << ", &texas = ";
cout << &texas << endl;
cout << "In oil(), x = " << x << ", &x = ";
cout << &x << endl;
{ // start a block
int texas = 113;
cout << "In block, texas = " << texas;
cout << ", &texas = " << &texas << endl;
cout << "In block, x = " << x << ", &x = ";
cout << &x << endl;
} // end a block
cout << "Post-block texas = " << texas;
cout << ", &texas = " << &texas << endl;
}
a.自动变量的初始化
b.自动变量和栈
(1)原理
(2)具体eg
c.寄存器变量register
3.静态持续变量
(1)C++静态存储持续性变量提供了3种链接性:
外部链接性(可在其它文件中访问);
内部链接性(只能在当前文件中访问);
无链接性(只能在当前函数或代码块中访问);
(2)这三种链接性变量的3个作用是:
最后的点3又称之为:静态变量的零初始化zero-initialized
(3)注意:
(4)如何创建这三种静态持续变量呢???:全局变量,静态全局变量以及静态局部变量
具体说明如下:
(5)总结:五种变量的存储方式对比,
以后别人问关于变量的问题都要从已下三个方面考虑!!!
说明:
(6)静态变量的初始化以及静态初始化与动态初始化的区别
4.静态链接性、外部链接性:针对常规全局变量,要在别的文件使用,则别的文件必须用extern声明
(1)外部变量也称之为全局变量
(2)单定义规则(one definition rule,ODR)
(3)声明使用关键字extern
eg:
补充:
(4)如果在函数中声明了一个与外部变量同名的变量,结果会如何??
external.cpp的代码为
// external.cpp -- external variable
// compile with support.cpp
#include <iostream>
// external variable
double warming = 0.3; // warming defined
// function prototypes
void update(double dt);
void local();
int main() // uses global variable
{
using namespace std;
cout << "Global warming is " << warming << " degrees.\n";
update(0.1); // call function to change warming
cout << "Global warming is " << warming << " degrees.\n";
local(); // call function with local warming
cout << "Global warming is " << warming << " degrees.\n";
// cin.get();
cin.clear();
while(cin.get()!='\n')
continue;
cin.get();
return 0;
}
support.cpp代码为
// support.cpp -- use external variable
// compile with external.cpp
#include <iostream>
extern double warming; // use warming from another file
// function prototypes
void update(double dt);
void local();
using std::cout;
void update(double dt) // modifies global variable
{
extern double warming; // optional redeclaration要不要都可以
warming += dt; // uses global warming
cout << "Updating global warming to " << warming;
cout << " degrees.\n";
}
void local() // uses local variable
{
double warming = 0.8; // new variable hides external one
cout << "Local warming = " << warming << " degrees.\n";
// Access global variable with the
// scope resolution operator
cout << "But global warming = " << ::warming;
cout << " degrees.\n";
}
说明:
(a)extern的具体用法
(b)对于函数update()使用extern重新声明warming变量的解释
(c)在local()函数中,局部变量和使用::运算符能够表示全局变量的具体解释如下:
(5)全局变量和局部变量的优缺点
5.静态持续性、内部链接性:针对于带static的变量
(1)
(2)如果要在其它文件中使用相同的名称来表示其它变量,该咋办??
首先是错误的eg如下:
接下来是正确的eg如下:
(3)eg:
twofile1.cpp中的代码为:
// twofile1.cpp -- variables with external and internal linkage
#include <iostream> // to be compiled with two file2.cpp
int tom = 3; // external variable definition
int dick = 30; // external variable definition
static int harry = 300; // static, internal linkage
// function prototype
void remote_access();
int main()
{
using namespace std;
cout << "main() reports the following addresses:\n";
cout << &tom << " = &tom, " << &dick << " = &dick, ";
cout << &harry << " = &harry\n";
remote_access();
// cin.get();
return 0;
}
twofile2.cpp的代码为:
// twofile2.cpp -- variables with internal and external linkage
#include <iostream>
extern int tom; // tom defined elsewhere
static int dick = 10; // overrides external dick
int harry = 200; // external variable definition,
// no conflict with twofile1 harry
void remote_access()
{
using namespace std;
cout << "remote_access() reports the following addresses:\n";
cout << &tom << " = &tom, " << &dick << " = &dick, ";
cout << &harry << " = &harry\n";
}
结果说明(懒得跑了,哈哈):
6.静态存储持续性、无链接性:针对于局部变量
(1)
(2)特点是:
(3)具体eg如下: 该程序演示了一种处理行输入可能长于目标数组的方法。
#include<stdio.h>
#include<stdlib.h>
// static.cpp -- using a static local variable
#include <iostream>
// constants
const int ArSize = 10;
// function prototype
void strcount(const char * str);
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') // string didn't fit!
cin.get(next); // dispose of remainder
strcount(input);
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize);
}
cout << "Bye\n";
// code to keep window open for MSVC++
/*
cin.clear();
while (cin.get() != '\n')
continue;
cin.get();
*/
system("pause");
return 0;
}
void strcount(const char * str)
{
using namespace std;
static int total = 0; // static local variable
int count = 0; // automatic local variable
cout << "\"" << str <<"\" contains ";
while (*str++) // go to end of string
count++;
total += count;
cout << count << " characters\n";
cout << total << " characters total\n";
}
说明:
(a)首先注意cin.get(input,ArSize)的用法
为啥为啥这么么写呢??
这是因为cin.get(XXX,XXX)的特点决定的!这在第四章的相关内容已经说的很明白了
所以那块的代码也可以这么写:
cin.getline(input, ArSize);
while (cin)
{
//cin.get(next);//关键:读取行输入后的代码,输入完一段字符串以后,再点击一下换行就到这里了,会输入给这里的cin,用于检测换行符\n
//while (next != '\n') // string didn't fit!
//cin.get(next); // dispose of remainder
strcount(input);
cout << "Enter next line (empty line to quit):\n";
cin.getline(input, ArSize);
}
最后,再看看下面的总结,应该就是一目了然了。
(b)
(c)
7.说明符和限定符
(1)常见的存储说明符如下
各个说明符的作用如下:
(a)在同一个声明中不能使用多个说明符,但是thread_local除外,它可与static或extern结合使用。
(b)C++11中,关键字auto用于自动类型推断;
(c)C++11中,关键字register只是显示地指出变量是自动的,而其它版本则用在声明中指示寄存器存储;
(d)C++11中,关键字static被用在作用域为整个文件的声明中,表示内链接性;被用作局部声明中,表示局部变量的存储特性为静态的。
(e)C++11中,关键字entern表明:引用声明,即声明引用在其它地方定义的变量。
(f)C++11中,关键字thread_local指出:变量的持续性与其所属线程的持续性相同。thread_local变量之于线程,犹如常规静态变量之整个程序。
(2)CV限定符
说明:
(a)const的作用是:内存被初始化后,程序便不能再对它进行修改;
(b)关键字volatile作用是:
(3)mutable——了解即可
(3)再谈const
(a)const使得全局变量的链接性变成为内部的
(b)我们前面所说的,是在一个cpp文件中,可以使用另外一个cpp文件中的外部链接性的全局变量,使用extern;
而这里是用头文件的方式,两个是不一样的!
(c)能够将常量定义放在头文件中的原因是什么??
(d)const的全局变量想要被外部的文件使用,需要做到:
8.函数和链接性
(1)和C语言一样,C++不允许在一个函数中定义另外一个函数,因此,所有函数的存储持续性都自动为静态的,即整个程序执行期间都是一直存在的。
(2)可以在函数原型中使用关键字extern,来指出函数是在另一个文件中定义的,不过这个是可选的(要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者由链接程序搜索的库文件)。
(3)还可以用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用。用法:必须同时在原型和函数定义中使用该关键字。
eg:
(4)强调一下内联函数链接性的使用
(5)C++在哪里查找函数
9.语言链接性
(1)C与C++语言链接性的区别为:
eg:
(2)如果要在C++程序中使用C库中预编译的函数,将出现什么情况?
10.存储方案和动态分配:new和定位new操作符
(1)动态内存
通常,编译器使用三块独立的内存:一块用于静态变量(可能更细)、一块用于自动变量、一块用于动态存储。
(2)eg:定义一个动态数组玩玩
(3)使用new运算符初始化
(4)new失败时
(5)new:运算符、函数和替换函数
(6)定位new运算符
(a)
说明:
(b)
#include<stdio.h>
#include<stdlib.h>
// 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 array
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 third 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;
system("pause");
return 0;
}
说明:
(i)第一点:
(ii)第二点:
(iii)第三点:定位new运算符是否要用delete来释放,要看具体情况,而常规的new运算符是需要使用delete来释放内存的
(c)定位new运算符的其他形式