c++primer第九章内存模型和名称空间学习笔记

单独编译

程序分为三步

函数定义和变量声明不能放在头文件中。

头文件经常包括的内容

结构声明可以放在头文件中。

头文件coordin.h代码

#ifndef COORDIN_H_
#define COORDIN_H_

struct polar
{
    double distance;
    double angle;
};
struct rect
{
    double x;
    double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);

#endif

头文件管理

源文件file1.cpp

#include <iostream>
#include "coordin.h"
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";
    return 0;
}
/* 文件管理有问题*/

源文件file2.cpp

#include <iostream>
#include <cmath>
#include "coordin.h"

void show_polar(polar dapos)
{
    using namespace std;
    const double Rad_to_deg = 57.295777951;

    cout << "Distance = " << dapos.distance;
    cout << ", angle = " << dapos.angle * Rad_to_deg;
    cout << " degrees\n";
}

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

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

三种存储数据方案的区别

作用域和链接

作用域(scope)表示名称在文件(翻译单元)的多大范围内可见。例如函数定义的变量和在函数定义之前的变量

链接性(linkage)表示名称如何在不同单元件共享。链接性为外部的可以在文件间共享,内部只能在一个文件中的函数共享。自动变量的名称没有共享性。

作用域:

局部只能在定义它的代码块中国可用。(自动变量)

全局(文件作用域)定义位置到文件结尾都可以用。

静态变量的作用域取决于被如何定义的。

函数原型定义的变量只在函数中可用。

在类中声明的成员作用域为整个类

在名称空间声明的变量的作用域为整个名称空间

函数的作用域可以是整个类或者整个名称空间,但不能是局部的,因为局部的话只对自己可见,因此不能被其他函数调用。

自动存储持续性

#include <iostream>
void oil(int x);
using namespace std;
int main()
{
    int texas = 31;
    int year = 1999;
    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;
    return 0;
}
void oil(int x)
{
    int texas = 5;

    cout << "In oil(),texas =" << texas << ",&texas =";
    cout << &texas << endl;
    cout << "In oil(),x =" << x << ",&x =";
    cout << &x << endl;
    {
        int texas = 112;
        cout << "IN block, texas = " << texas;
        cout << ". &texas = " << &texas << endl;
        cout << "In block,x =" << x << ",&x =";
        cout << &x << endl;
    }
    cout << "post-block texas = " << texas;
    cout << ", &texas = " << &texas << endl;
}

auto表示默认状态下自动的变量,一般不适用。

自动变量初始化

声明时可以用已知的表达式来初始化自动变量。

自动变量和堆栈

由于自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用的方法是留出一段内存,并将其视为堆栈,以管理变量的增减。之所以被称为堆栈,是由于新数据被象征性地放在原有数据的上面(也就是说,在相邻的内存单元中,而不是在同一个内存单元中),当程序使用完后,将其从堆栈中删除。堆栈的默认长度取决于实现,但编译器通常提供改变堆栈长度的选项。

程序使用两个指针来跟踪堆栈,--个指针指向栈底--堆栈的开始位置,另一个指针指向堆顶--下-个可用内存单元。当函数被调用时,其自动变量将被加入到堆栈中,栈顶指针指向变量后面的下一个可用的内存单元。函数结束时,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。

寄存器变量

C++也支持使用register 关键字来声明局部变量。寄存器变量是另一种形式的自动变量,因此其存储持续性为白动,作用域为局部,但没有链接性。关键字register 提醒编译器,用户希望它通过使用CPU寄存器,而不是堆栈来处理特定的变量,从而提供对变量的快速访问。这里的理念是,CPU访问寄存器中的值的速度比访问堆栈中内存快。要声明寄存器变量,请在类型前加上关键字register。

静态持续变量

C++也为静态存储持续性变量提供了3种链接性:

  • 外部链接性、
  • 内部链接性
  • 无链接性

由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如堆栈)来管理它们。编译器将分配周定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。

在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0

要想创建链接性为外部的静态持续变量,必须在代码块的外面声明它

要创建链接性为内部的静态持续变量,必须在代码块的外面声明它并使用静态限定符static

要创建没有链接性的静态持续变量,必须在代码块内声明它,并使用静态限定符static

静态持续性,外部链接性

链接性为外部的变量通常简称为外部变量,它们的存储持续性为静态,作用域为整个文件。外部变量是在函数外部定义的,因此对所有函数而言都是外部的。

因此外部变量也称全局变量(相对于局部的自动变量)。

不过,如果定义了与外部变量同名的自动变量,则当程序执行其所属的函数时,该自动变量将隐藏同名的外部变量。

下列程序说明了这几点,它还演示了如何使用关键字extem 来重新声明以前定义过的外部变量,以及如何使用C++的作用域解析操作符来访问被隐藏的外部变量。

#include <iostream>
using namespace std;

double warming = 0.3;

void update(double dt);
void local();

int main()
{
    cout << "Global warming is " << warming << " degrees. \n";
    update(0.1);
    cout << "Global warming is " << warming << " degrees. \n";
    local();
    cout << "Global warming is " << warming << " degrees. \n";
    return 0;
}

void update(double dt)
{
    extern double warming; // 引用声明
    warming += dt;
    cout << "updating global warming to " << warming;
    cout << " degrees.\n";
}
void local()
{
    double warming = 0.8;

    cout << "Local warming = " << warming << " degrees. \n";

    cout << "But global warming = " << ::warming;
    cout << "degrees. \n";
}

extern double warming:称为引用声明(referencing declaration),或简称为声明(declaration)。

它不给变量分配存储空间,因为它引用已有的变量。只能在引用其他地方(或函数)定义的变量的声明中使用关键字extern。

在引用声明中指定的类型应与定义声明中相同;另外,不能在引用声明中初始化变量

静态持续性,内部链接性

将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。

在多文件程序中,内部链接性和外部链接性之间的差别很有意义。链接性为内部的变量只能在其所属的文件中使用。

但常规外部变量都具有外部链接性,即可以在其他文件中使用。对于外部链接性变量,有且只有一个文件中包含了该变量的外部定义。其他文件要使用该变量,必须在引用声明中使用关键字extern

如果文件没有提供变量的extern声明,则不能使用在其他文件中定义的外部变量:

如果文件试图定义另一个同名的外部变量将出错

正确的方法是使用extern

但如果文件定义了一个静态外部变量,其名称与另一个文件中声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量:

应使用外部变量在多文件程序的不同部分之间共享数据:应使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据(名称空间提供了另外一种共享数据的方法,C++标准指出,使用 static 来创建内部链接性的方法将逐步被淘汰)。另外,如果将作用域为整个文件的变量变为静态的,就不必担心其名称与其他文件中的作用域为整个文件的变量发生冲突。

file1.cpp

// 静态会覆盖外部
#include <iostream>
int tom = 3;
int dick = 30;
static int harry = 300;
// to be compiled with two file2.cpp//external variable definition//external variable definition
// 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();
    return 0;
}

file2.cpp

// twofile2.cpp--variables with internal and external linkage
#include <iostream>
extern int tom;
static int dick = 10;
int harry = 200;
void remote_access()
{
    using namespace std;
    cout << "remote access()reports the following addresses; \n";
    cout << &tom << "= &tom, " << &dick << " n = &dick, ";
    cout << &harry << "= &harry\n";
}

静态存储持续性,无链接性

无链接性的局部变量。

这种变量是这样创建的,将static 限定符用于在代码块中定义的变量。在代码块中使用static时,将导致局部变量的存储持续性为静态的。这意味着虽然该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将保持不变

另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化

// 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);
        strcount(input);
        cout << "Enter next line(empty line to quit);\n";
        cin.get(input, ArSize);
    }
    cout << "Bye\n";
    return 0;
}
void strcount(const char *str)
{
    using namespace std;
    static int total = 0;
    int count = 0;
    // static local variable
    // automatic local variable
    cout << "\"" << str << "\"contains \n";
    while (*str++) // go to end of string
        count++;
    total += count;
    cout << count << " characters\n";
    cout << total << " characters total\n";
}

说明符和限定符

存储说明符(storage class specifier)

cv-限定符

const

在默认情况下全局变量的链接性为外部的,但const 全局变量的链接性为内部的。也就是说,在C++看来,全局const定义(如下述代码段所示)就像使用了 static 说明符一样。

如果个局const声明的链接性像常规变量那样是外部的,则这将出错,

其他文件必须使用extem 关键字来提供引用声明。另外,只有未使用extem关键字的声明才能进行初始化:

因此,需要为某个文件使用一组定义,而其他文件使用另一组声明。然而,由于外部定义的const 数据的链接性为内部的,因此可以在所有文件中使用相同的声明。

函数和链接性

和C语言一样,C++不允许在一个函数中定义另外一个函数,因此所有函数的存储持续性都自动为静态的

在默认情况下,函数的链接性为外部的,即可以在文件间其字,实际上,可以在函数原型中使用关键字 extern米指出函数是在另一个文件中定义的,不过这是可选的

还可以使用关键字 static 将函数的链接性设置为内部的,使之只能在一个文件中使用。必须同时在原型和函数定义中使用该关键字。

语言的链接性

存储方案和动态分配

new delete

动态内存不是LIFO,其分配和释放顺序要取决于new和 delete 在何时以何种方式被使用。通常,编译器使用3块独立的内存:一块用于静态变量(可能再细分),一块用于自动变量,另外一-块用于动态存储。

布局new操作符

ew操作符还有另一种变体,被称为布局new操作符,它让您能够指定要使用的位置。

述代码从buffer1中分配空间给结构chaff,从buffer2中分配空间给一个包含 20 个元素的 int 数组。

// 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 << "Buffer addresses;\n"
         << " heap;" << pd1 << " static;"
         << (void *)buffer << endl;
    cout << " Buffer contents;\n";
    for (i = 0; i < N; i++)
    {
        cout << pd1[i] << &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];
    pd4 = new (buffer) double[N];
    for (i = 0; i < N; i++)
        pd4[i] = pd3[i] = 1000 + 20.0 * i;
    cout << "Buffer 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 + 20.0 * i;
    cout << "Buffer 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;
    return 0;
}

没有使用delete来释放使用布局new 操作符分配的内存。事实上,在这个例子中不能这样做。buffer 指定的内存是静态内存,而 delete 只能用于这样的指针:指向常规 new 操作符分配的堆内存。也就是说,数组bufer位于delete的管辖区域之外

名称空间

传统的C++名称空间

第一个需要知道的术语是声明区域(declaration region)。

例如,可以在函数外面声明全局变量,对于这种变量,其声明区域为其声明所在的文件。对于在函数中声明的变量,其声明区域为其声明所在的代码块。

第二个需要知道的术语是潜在作用域(potentialscope)。变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域比声明区域小,这是由于变量必须定义后才能使用。

C++关于全局变量和局部变量的规则定义了一种名称空间层次。每个声明区域都可以声明名称,这些名称独立于在其他声明区域中声明的名称。在一个函数中声明的局部变量不会与在另一个函数中声明的局部变量发生冲突。

新的名称空间

一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。例如,下面的代码使用新的关键字 namespace 创建了两个名称空间:Jack和J。

需要有一种方法来访问给定名称空间中的名称。最简单的方法是,通过作用域解析操作符::使用名称空间来限定该名称

using 声明和using编译指令

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

using 声明使一个名称可用,而 using 编译指令使所有的名称都可用。using编译指令由名称空间名和它前面的关键字 using namespace 组成,它使名称空间中的所有名称都可用,而不需要使用作用域解析操作符::

using 编译指令和 using 声明之比较

使用 using 编译指令导入个名称空间中所有的名称与使用多个using 声明是不一样的,而更像是大量使用作用域解析操作符。

使用using声明时,就好像声明了相应的名称一样。如果某个名称已经在函数中声明了,则不能用 using 声明导入相同的名称。然而,使用 using 编译指令时,将进行名称解析,就像在包含using 声明和名称空间本身的最小声明区域中声明了名称一样。

在下面的范例中,名称空间为全局的。如果使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名,就像隐藏同名的全局变量一样。

名称空间的其他特性

例子

namesp.h

namespace pers
{
    const int Len = 40;
    struct Person
    {
        char fname[Len];
        char lname[Len];
    };
    void gerPerson(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);
}

namesp.cpp

#include <iostream>
#include "namesp.h"

namespace pers
{
    using std::cin;
    using std::cout;
    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;
    }
}

namessp.cpp

#include <iostream>
#include "namesp.h"

void other(void);
void another(void);
int main(void)
{
    using debts::Debt;
    using debts::showDebt;
    Debt golf = {{"Benny", "Goat"}, 120.0};
    showDebt(golf);
    other();
    another();

    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", "Right"};
    pers::showPerson(collector);
    std::cout << std::endl;
}

名称空间及其前途

相关推荐
sanguine__9 分钟前
Web APIs学习 (操作DOM BOM)
学习
冷眼看人间恩怨21 分钟前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客30 分钟前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin33 分钟前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos2 小时前
c++---------数据类型
java·jvm·c++
数据的世界012 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐2 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
十年一梦实验室3 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0013 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我583 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物