读书笔记||内存模型和名称空间

一、单独编译

预期将结构声明加入到每一个文件中,不如将其放在头文件中,然后在每一个源代码文件中包含该头文件,这样,要修改结构声明时,只需要在头文件中做一次改动即可。另外,也可以将函数原型放在头文件中,因此,可以将原来得程序分成三部分。
头文件:包含结构声明和使用这些结构的函数的原型。
源代码文件:包含与结构有关的函数的代码。
源代码文件:包含调用与结构相关的函数的代码。
这是一种非常有用的组织程序的策略。一个文件包含了用户定义类型的定义;另一个文件包含操纵用户定义类型的函数的代码。这两个文件组成了一个软件包,可用于各种程序中。
不要将函数定义或变量声明放到头文件中,这样做通常会引起不必要的麻烦。例如,如果头文件包含一个函数的定义,然后在其他两个文件(属于同一个程序)中包含该头文件,则同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则这将出错。
头文件中常包含的内容

  • 函数原型
  • 使用#define或const定义的符号常量。
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数
    将结构声明放在头文件中是可以的,因为它们不创建变量,而只是在源代码文件中声明结构变量时,告诉编译器如何创建该结构变量。同样,模板声明不是将被编译的代码,它们只是编译器如何生成与源代码中的函数调用相匹配的函数定义。被声明为const的数据和内联函数有特殊的链接属性,因此将其放在头文件中,而不会引起问题。
    在包含自己的头文件的时候,我们使用的是双引号而不是尖括号。因为如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录(或其他目录,这取决于编译器)。如果在那没有找到头文件的话,将在标准位置查。
#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
#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; 
}
#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";
}

在这里插入图片描述
同一个文件中只能将同一个头文件包含一次。但是很有可能在不知情的情况下将头文件包含多次。基于预处理器编译指令#ifndef(if not defined)
仅当以前没有使用预处理器编译指令#define定义名称COORDIN_H_时,才处理#ifndef和#endif之间的语句:

#ifndef COORDIN_H_
……
#endif

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

  • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的持续性为自动的。它们在程序开始执行其所属的函数和代码块是被创建,在执行完函数或代码块时,它们使用的内存被释放。
  • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。
  • 线程存储持续性:多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量时是使用关键字thread_local声明的,则其声明周期与所属的线程一样长。
  • 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被成为自由存储或堆。
    1.作用域或链接
    作用域(scope)描述了名称在文件(翻译单元)的多大范围内可见。例如,函数中定义的变量可在该函数中使用,但不能在其他函数中使用;而在文件中的函数定义之前定义的变量则可在所有函数中使用。链接性(linkage)描述了名称如何在不同单元间的共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。
    作用域为局部的变量只在定义它的代码块中可用。代码块是由花括号括起的一系列语句。例如函数体就是代码块,但可以在函数体中嵌入其他代码块。作用域为全局的变量在定义位置到文件结尾之间都可用。自动变量的作用域为局部,静态变量的作用域是全局还是局部取决于它是如何被定义的。在函数原型作用域(function prototype scope)中使用的名称只在包含参数列表的括号内可用。在类中声明的成员的作用域为整个类。在名称空间中声明的变量的作用域是整个名称空间。
    C++函数的作用域可以是整个类或者整个名称空间,但不能是局部的。
    2.自动存储持续性
    默认情况下,在函数中声明的函数参数和变量的存储持续性为自动的,作用域为局部的,没有链接性。也就是说,在main()中声明了一个名为texas的变量,并在函数oil()中也声明了一个名为texas变量,则创建了两个独立的变量——只有在定义它们的函数中才能使用它们。对于oil()函数中的texas中执行的任何操作都不会影响到main()中的texas,反之亦然。
    另外,当程序开始执行这些变量所属的代码块时,将为其分配内存,当函数结束的时,这些变量都将消失。
#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();
    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;
}

在这里插入图片描述

3个texas变量的地址个各不相同,而程序使用当前可见的那个变量,因此将113赋给oil()中的内部代码块中的texas,对其他同名变量没有影响。同样,实际的地址值和地址格式随系统而异。执行到main()时,程序为texas和year分配空间,使得这些变量可见。当程序调用oil()时,这些变量仍留在内存中,但不可见。为两个新变量(x和texas)分配内存,从而使它们可见。在程序执行oil()的内部代码块时,新的texas将不可见,它被一个更新的定义替代。然而,变量x仍然可见,这是因为该代码块没有定义x的变量。当程序流程离开该代码块时,将释放最新的texas使用的内存,而第二个texas再次可见。当oil()函数结束时,texas和x都将过期,而最初的texas和year再次变得可见。
3.静态持续变量
C++也为静态存储持续性变量提供3种链接性:外部链接(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置来管理他们,编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。
4.静态持续性、外部链接性
链接性为外部的变量通常简称为外部变量,它们的存储持续性为静态,作用域为整个文件夹。外部变量是在函数外部定义的,因此对所有函数而言都是外部的。例如,可以在main()前面或者头文件中定义它们。可以在文件中位于外部变量定义后面的任何函数中使用它,因此外部变量也称为全局变量。

#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();
    return 0;
}
#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";
}

在这里插入图片描述
main()和update()都可以访问外部变量warming。update()修改了warming,这种修改再随后使用该变量时显现出来。
double warming=0.3;
在第二个程序中,使用关键字extern声明变量warming,让该文件中的函数能够使用它:
extern double warming // 使用外部定义的变量warming。
另外,函数update()使用关键字extern重新声明了变量warming,这个关键字的意思是,通过这个名称使用在外部定义的变量。
5.静态持续性、内部链接性
将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。在多文件程序中,内部链接性和外部链接性之间的差别是很有意义的。链接性为内部的变量只能在其所属的文件中使用;但常规外部变量都具有外部链接性,即可以在其他文件中使用。

#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; 
}
#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";
}

在这里插入图片描述
第一个文件定义了外部变量tom和dick以及静态外部变量harry,在main()函数中显示三个变量的地址,然后再调用在另一个文件中定义的remote_access()函数,另一个文件还用extern关键字来与第一个文件共享tom,此外,还定义了一个dick的静态变量。static限定符使用该变量被限制在这个文件内,并覆盖相应的全局定义。harry的外部变量会与第一个文件中的harry发生冲突,因为后者的链接性为内部。
两个文件使用同一个tom变量,但使用不同的dick和harry变量。两个tom变量的地址相同,而两个dick和harry变量的地址不同。
6.静态存储持续性、无链接性
将static限定符用于在代码中定义的变量。在代码块中使用static时,将导致局部变量的存储持续性为静态。这意味着虽然该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。因此在两次函数的调用之间,静态局部变量的值保持不变。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。

#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();
*/
    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";
}

在这里插入图片描述
7.说明符号和限定符
存储说明符:

  • auto C++11之前指出变量为自动变量;C++11之后用于自动类型推断。
  • register 用于在声明中指示寄存器存储;在C++11中,只是显示地指出变量是自动的。
  • static 用在作用域为整个文件的声明中,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的。
  • extern 引用声明,声明引用在其他地方定义的变量。
  • thread_local 变量的持续性与其所属线程的持续性相同,变量之于线程,犹如常规静态变量之于整个程序。
  • mutable
    在同一个声明中不能使用多个说明符,但thread_local除外,它可与static或extern结合使用。
    1.cv-限定符(cv表示const和volatile)
    const表明内存被初始化后,程序便不能再对它进行修改,
    volatile表明即使程序代码没有对内存单元进行修改,其值也可能发生变化。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序再几条语句中两次使用某个变量的值,则编译器可能不是让程序查找这个词两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会变化。
    2.mutable
    即使结构的变量为const,某某成员也可以被修改。
    8.函数和链接性
    和变量一样,函数也有链接性,虽然可选择的范围比变量小。在默认情况下,函数的链接性为外部的,即可以在文件间共享。实际上,可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的,还可以使用关键字static将函数的链接性设置为内部的,使之只能能在一个文件中使用。必须同时在原型和函数定义中使用该关键字。
    9.语言链接性
    链接程序要求每个不同的函数都有不同的符号名。在C++中,同一个函数可能能对应多个函数,必须将这些函数翻译成不同的符号名称。
    10.存储方案和动态分配
    使用new运算符初始化
    如果要为内置的标量类型分配存储空间并初始化,可在类型名后面加上初始值,并将其用括号括起:int *pi = new int (6)这种括号语法也可用于有合适的构造函数的类。
    new:运算符、函数和替换函数
    运算符new和new[]分别调用如下函数:
    void * operator new(std::size_t);
    void * operator new[] (std::size_t);
    这些函数被称为分配函数,它们位于全局名称空间中。同样,也由delete和delete[]调用的释放函数
    void operator delete(void *);
    void operator delete[] (void *);
    定位new运算符
    new负责在堆中找一个足以能够满足要求的内存块。new运算符还有一种变体,被称为定位new运算符,能够指定要使用的位置。
    要使用定位new特性,首先需要包含头文件new,它提供了这种版本的new运算符的原型,然后将new运算符用于提供了所需地址的参数。除需要特定参数外,句法与常规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;
    // cin.get();
    return 0;
}

在这里插入图片描述
定位new运算符确实将数组p2放在了数组buffer中,p2和buffer的地址是一样的。然而它们的类型不同,p1是double指针,而buffer是char指针。同时,常规new将数组p1放在很远的地方,位于动态管理的堆中。第二,第二个常规new运算符查找一个新的内存块,分配与以前相同的内存块,定位new运算符使用传递给他的地址,它不跟下哦那个哪些内存单位已被使用,也不查找未使用的内存块。在第三次调用定位new运算符时,提供了一个从数组buffer开头算起的偏移量。第三,是否使用delete来释放内存。

三、名称空间

声明区域:可以在其中进行声明的区域。例如,可以在函数外面声明全局变量,对于这种变量,其生命区域为其声明所在的文件。对于在函数中声明的变量,其声明区域为其声明所在的代码块。
潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域比声明区域小,这是由于变量必须定义以后才能使用。
using声明和using编译指令
不希望每次使用名称时都对它进行限定,因此C++提供两个机制(using声明和using编译指令)来简化对名称空间中的名称的使用。using声明使特定的标识符可用,using编译指令使整个名称空间可用。
using声明使一个名称可用,而using编译指令使所有的名称都可用。using编译指令由名称空间名和它前面的关键字using namespace组成,它使名称空间中的所有名称都可用,而不需要使用作用域解析运算符。
使用using编译指令导入一个名称空间中所有的名称与使用多个using声明使不一样的,而更像是大量使用作用域解析运算符。使用using声明时,就好像声明了相应的名称一样。

#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); 
}
#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;
    }
}
#include <iostream>
#include "namesp.h"
void other(void);
void another(void);
int main(void)
{
    using debts::Debt;
 using debts::showDebt;
    Debt golf = { {"Benny", "Goatsniff"}, 120.0 };
    showDebt(golf);
    other();
    another(); 
 // std::cin.get();
 // std::cin.get();
    return 0;
}
void other(void)
{
    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(void)
{
    using pers::Person;;   
    Person collector = { "Milo", "Rightshift" };
    pers::showPerson(collector);
    std::cout << std::endl; 
}

在这里插入图片描述
包含头文件中常包含的内容:常量、结构定义和函数原型。
在上述例子中,第一个名称空间叫做pers,其中包含Person结构的定义和两个函数的原型——一个函数用人名填充,另一个函数显示结构的内容;第二个名称孔空间叫做debts,它定义一个结构,该结构用来存储人名和金额。该结构使用了Person结构,因此,debts名称空间使用一条using编译指令,让pers中的名称在debts名称空间可用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值