• C++学习:数据的存储、作用域、链接


    一、数据的存储方式

    C++中使用3种不同的方案来存储数据,不同方案的区别在于数据在内存中保留的时间。

    1、自动存储

    函数定义中声明的变量,以及函数的参数,是自动存储的。在程序执行对应函数的时候创建这些变量,对应的函数执行完之后,它们使用的内存被释放。也就是所谓的局部变量。

    2、静态存储

    函数定义外的变量、以及使用static关键字定义的变量都是静态存储的,它们在程序的整个运行过程中都是存在的。函数定义外的变量有时候可以理解为全局变量。

    3、动态存储

    使用new运算符分配的内存,在使用delete运算符将其释放之前或者程序结束之前,它一直都存在,这种存储方式就是动态存储,也称为自由存储(free store),或堆(heap)。

    二、作用域(scope)

    作用域用来描述某个名称在文件的可见范围,这个名称有时候可以理解成变量。

    例如,函数中定义的变量可以在该函数中使用,但不能在其他函数中使用;

    函数定义之前定义的变量,可以在所有的函数中使用;

    C++变量的作用域有多种:

    作用域为局部的变量只能在自定义它的代码块中使用。自动变量就是一种局部变量。所谓代码块就是用{ }括起来的一系列语句,比如,函数体就是一种代码块。代码块还可以嵌套代码块 { { } }。

    作用域位全局的变量,从定义的位置开始,到文件结尾都是可以使用的。

    静态变量的作用域是全局还是局部,要看它是怎么定义的。

    C++的函数作用域可以是整个类,或者整个名称空间,但不能是局部的。

    三、链接性(linkage)

    链接性用来描述某个名称,是如何在不同的单元之间共享的。

    自动变量的名称没有链接性,因此不能共享。自动变量更多的可以理解为局部变量,用完就没了,就像太监一样,不会有后代,他的一生过完之后就没人会记得他。

    四、自动变量

    默认情况下,函数中声明的函数参数,变量都是自动存储的,也叫做自动变量,作用域是局部,没有链接性。

    比如,在main函数中声明一个变量texas;

    在函数oil()函数中也声明一个变量texas;

    这两个texas是独立的,不会相互影响,他们只能定义它们的函数中使用。就像湖北有个小明,浙江也有个小明,他们在不同的省,他们之间一点关系都没有。

    程序示例1:自动存储

    1. #include
    2. void oil(int x);
    3. int main()
    4. {
    5. using namespace std;
    6. int texas = 31; //在main函数中定义的texas
    7. int year = 2023;
    8. cout << "1:in main(), texas = " << texas << ", &texas = " << &texas << endl;
    9. cout << "2:in main(), year = " << year << ", &year = " << &year << endl;
    10. oil(texas);
    11. cout << "8:in main(), texas = " << texas << ", &texas = " << &texas << endl;
    12. cout << "9:in main(), year = " << year << ", &year = " << &year << endl;
    13. return 0;
    14. }
    15. void oil(int x)
    16. {
    17. using namespace std;
    18. int texas = 5; //在oil函数中定义的texas
    19. cout << "3:in oil(), texas = " << texas << ", &texas = " << &texas << endl;
    20. cout << "4:in oil(), x = " << x << ", &x = " << &x << endl;
    21. //开启代码块
    22. {
    23. int texas = 113; //在代码块中定义的texas
    24. cout << "5:in block, texas = " << texas << ", &texas = " << &texas << endl;
    25. cout << "6:in block, x = " << x << ", &x = " << &x << endl;
    26. } //代码块结束
    27. cout << "7:out of block, texas = " << texas << ", &texas = " << &texas << endl;
    28. }

     输出结果:

    1. 1:in main(), texas = 31, &texas = 0x61fe1c
    2. 2:in main(), year = 2023, &year = 0x61fe18
    3. 3:in oil(), texas = 5, &texas = 0x61fddc
    4. 4:in oil(), x = 31, &x = 0x61fdf0
    5. 5:in block, texas = 113, &texas = 0x61fdd8
    6. 6:in block, x = 31, &x = 0x61fdf0
    7. 7:out of block, texas = 5, &texas = 0x61fddc
    8. 8:in main(), texas = 31, &texas = 0x61fe1c
    9. 9:in main(), year = 2023, &year = 0x61fe18

    结果分析:

     分别在3个不同的位置定义了3个texas变量,地址分别是0x61fe1c、0x61fddc、 0x61fdd8,说明他们名称相同,但是属于不同的东西,可以同时存在。

    在执行到main的时候,程序为1号taxas和year分配空间,使这两个变量可见;

    当程序调用oil()时,前面的1号taxas和year变量仍留在内存中,但是不可见,程序为两个新的变量x和2号taxas分配内存,使它们可见;

    当程序执行到oil()内部的代码块的时候,2号taxas将不可见,它被一个更新的3号taxes代替,但是变量x依然可见,这是因为该代码块没有定义x变量。

    当程序执行到离开代码块的时候,将释放3号taxes所使用的内存,2号taxes重见天日。

    当oil()函数执行完之后,2号taxes和x都过期,1号taxes和year也重见天日了。

    自动变量与栈

    C++编译器如何实现自动变量

    自动变量的数目随着函数的开始而增加,随着函数的结束而减少,因此程序必须对自动变量进行管理。常见的管理方法是流出一段内存来存储这些自动变量,这段内存就是常说的

    这段内存为什么被称为栈?栈是先进后出的,LIFO,即最后加入到栈中的变量,最先被使用。就像弹夹里面压子弹一样,最后压入的子弹,最先被射出。

    因为存储的时候,新数据被象征性地放在原有数据的上面,当程序使用完之后,将其从栈中删除。

    程序使用两个指针来跟踪栈,

    一个指针指向栈底----栈的开始位置;

    一个指针指向栈顶----下一个可用的存储单元。

    当函数被调用时,函数里面的自动变量被加入到栈中,栈顶指针指向变量后面的一个可用的存储单元。当函数结束时,栈顶指针被重置为函数被调用前的值,释放内存。

    自动变量在栈中的变化示意图

    假设函数fib(short ms, int mi)被调用,传递2个字节的short和4个字节的int

    1、函数调用前的栈(每个方框代表2个字节)

    2、函数调用后的栈,函数调用fib(18, 50),将参数压入栈

    3、函数开始执行后的栈 fib(short ms, int mi),函数执行时,将形参ms,mi与栈中的值关联起来

    4、函数结束后,栈顶的指针恢复到原来的位置,但是栈中的数据并没有消失,可以后续被覆盖。

     

     五、静态变量的链接性

    与C语言一样,C++也为静态变量提供3种链接性:

    外部链接性----可在其他文件中访问;

    内部链接性----只能在当前文件中访问;

    无链接性----只能在当前函数或者代码块中访问;

    这3种链接性都在程序的整个执行期间存在,与自动变量相比,它们的寿命更长。

    静态变量的数目在程序运行期间是不变的,不需要使用栈来管理它们,编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个执行期间一直存在。

    如果没有显式的初始化静态变量,编译器会把它们设置为0。

    要想创建链接性为外部的静态变量,必须在代码块的外面声明它,有点像全局变量;

    要想创建链接性为内部的静态变量,必须在代码块的外面声明它,并使用static关键字;

    要创建没有链接性的静态变量,必须在代码块内声明它,并使用static关键字

    程序示例2:三种静态变量的创建

    1. #include
    2. int global= 1000; //创建一个有外部链接性的静态变量,这个工程的所有文件都可以使用它
    3. static int s = 50; //创建一个有内部链接性的静态变量,只有在当前源文件使用它
    4. void func();
    5. int main()
    6. {
    7. using namespace std;
    8. cout << "global = " << global << " and &global = "<< &global <
    9. cout << "s = " << s << " and &s = "<< &s <
    10. func();
    11. return 0;
    12. }
    13. void func()
    14. {
    15. using namespace std;
    16. static int count ; //创建一个无链接性的静态变量,编译器 默认赋值0,只有当前函数可以使用它
    17. cout << "count = " << count << " and &count = "<< &count <
    18. }

    六、外部链接性的静态变量

    链接性为外部的变量,简称为外部变量,存储持续性为静态,作用域是整个文件。外部变量是在函数的外部定义,对所有的函数来说都是外部的,在它后面的函数都可以使用它,因此也称它为全局变量

    外部变量(全局变量)的定义规则:

    1、每个使用外部变量的文件,都必须声明它;

    2、变量只能定义一次。

    因此C++提供两种变量声明:

    1、定义声明,简称定义,它给变量分配存储空间;

    2、引用声明,简称声明,它不给变量分配存储空间,因为它只是引用已有的变量,使用关键字extern

    1. double up; //这属于定义,默认初始值为0
    2. extern int blem; //这属于引用,blem在别的地方定义了;
    3. extern char gr = 'z'; //这属于定于,因为有初始化,虽然他有extern关键字

    如果要在多文件中使用外部变量,只需要在一个文件中进行该变量的定义,但在使用该变量的其他所有文件中,都必须使用关键字extern来声明它。

    1. //file1.cpp
    2. extern int cat = 20; //在文件file1.cpp中定义
    3. int dog = 22; //在文件file1.cpp中定义
    4. int flees; //在文件file1.cpp中定义
    5. //file2.cpp,file2中没有重新声明flees变量,所以在file2中不能使用flees
    6. extern int cat;//在文件file2.cpp中使用file1.cpp中定义的cat,要加extern关键字,但不能重复定义
    7. extern int dog;//在文件file2.cpp中使用file1.cpp中定义的dog,要加extern关键字,但不能重复定义
    8. //filen.cpp
    9. extern int cat;//在文件filen.cpp中使用file1.cpp中定义的cat,要加extern关键字,但不能重复定义
    10. extern int dog;//在文件filen.cpp中使用file1.cpp中定义的dog,要加extern关键字,但不能重复定义
    11. extern int flees;//在文件filen.cpp中使用file1.cpp中定义的flees,要加extern关键字,但不能重复定义

    七、内部链接性的静态变量

    链接性为内部的静态变量,简称静态外部变量。这名字有点拗口,与前面的外部变量要加以区分,只要是定义在函数外部的就是外部变量,定义在函数外部的,又加了static关键的字就叫静态外部变量。内部链接性的静态变量与外部链接性的静态变量在单文件系统使用中是没有区别的,只有在多文件系统使用时才有区别。内部链接性的静态变量只能作用于当前文件,外部链接性的静态变量能作用于整个工程的全部文件。

    程序示例3:链接性为外部和内部的静态变量交叉使用

    file1.cpp

    1. #include
    2. int tom = 3; //定义一个外部变量tom
    3. int dick = 30; //定义一个外部变量dick
    4. static int harry = 300; //定义一个静态外部变量harry,仅限于file1.cpp使用
    5. void remote_access();
    6. int main()
    7. {
    8. using namespace std;
    9. cout << "main() reports the following address:\n";
    10. cout <<"&tom = "<< &tom <<", &dick = "<< &dick <<", &harry = "<<&harry << endl;
    11. cout << endl;
    12. remote_access();
    13. return 0;
    14. }

    file2.cpp

    1. #include
    2. extern int tom;//属于声明,引用file1.cpp中定义的tom
    3. static int dick = 10;//在file2.cpp中重新定义
    4. int harry = 200;//重新定义一个外部变量
    5. void remote_access()
    6. {
    7. using namespace std;
    8. cout << "remote_access() reports the following address:\n";
    9. cout <<"&tom = "<< &tom <<", &dick = "<< &dick <<", &harry = "<<&harry << endl;
    10. }

    输出结果:

    1. main() reports the following address:
    2. &tom = 0x403020, &dick = 0x403024, &harry = 0x403028
    3. remote_access() reports the following address:
    4. &tom = 0x403020, &dick = 0x403010, &harry = 0x403014

    从打印的地址就可以看出来,file1和file2使用的tom是同一个变量,dick和harry市不同的变量。

    八、无链接性的静态变量

    无链接性的静态变量,就是在函数内部,或者代码块内部,也称为静态局部变量。用static关键字定义的变量。我们知道在函数体内或者代码块内定义的变量就是局部变量,存在栈中,函数或代码块执行完,变量就没了,现在将这种变量前面加个static关键字,就是强行续命,即使函数或代码块不处于活动的时候,变量依然存在。

    如果初始化了静态局部变量,程序只在启动的时候进行一次初始化,以后再调用函数时,不会想自动变量那样重新初始化。

    1. #include
    2. const int ArSize = 10;
    3. void strcount(const char* str);
    4. int main()
    5. {
    6. using namespace std;
    7. char input[ArSize];
    8. char next;
    9. cout << "enter a line:\n";
    10. cin.get(input, ArSize);
    11. while(cin)
    12. {
    13. cin.get(next);
    14. while(next !='\n')
    15. cin.get(next);
    16. strcount(input);
    17. cout <<"enter next line:\n";
    18. cin.get(input,ArSize);
    19. }
    20. cout << "Bye\n";
    21. return 0;
    22. }
    23. void strcount(const char* str)
    24. {
    25. using namespace std;
    26. static int total = 0;
    27. int count = 0;
    28. cout<<"\"" << str <<"\"contains";
    29. while(*str++)
    30. {
    31. count++;
    32. }
    33. total += count;
    34. cout << count << " characters\n";
    35. cout << total << " characters total\n";
    36. }

    因为数组长度是10,因此程序从每行读取的字符串都不超过9个,每次函数调用时,自动变量count都被重置为0,但是静态变量total只在程序运行时被设置为0,以后再两次函数调用之间,其值保持不变,因此能记录输入字符的总数。

    九、const关键字对存储类型的影响

    在C++中,const限定符对默认存储类型稍有影响。在默认情况下,全局变量的链接性是外部的,但是const修饰的全局变量链接性是内部的。也就是说,在C++看来,const定义的全局变量与static说明符是一样的。也就是说下面的代码1和代码2是等效的。

    代码1:

    1. const int fingers = 10;
    2. int main()
    3. {
    4. ......
    5. }

    代码2:

    1. static int fingers = 10;
    2. int main()
    3. {
    4. ......
    5. }

    十、函数的链接性

    和变量一样,函数也有链接性,但是可选择的范围比变量小。

    与C语言一样,C++也不允许在一个函数中再定义另一个函数,因此所有函数的存储持续性都是静态的,因为都是在函数外定义,在整个程序执行期间都是存在的。

    默认情况下,函数的链接性是外部的,即可以再文件间共享。

    在实际使用过程中,也可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的;

    还可以使用static关键字将函数的链接性设置为内部的,使它只能在一个文件中使用,这种操作必须在函数原型和函数定义中都加static关键字。

    1. static int private(double x);//函数原型前加static
    2. static int private(double x) //函数定义前加static
    3. {
    4. ......
    5. }

  • 相关阅读:
    【笔记】文献阅读[SORT]-SIMPLE ONLINE AND REALTIME TRACKING
    「SpringCloud」04 Ribbon负载均衡服务调用
    HZOJ-838:2020年数据结构41题
    图注意力和序列图模型
    CesiumJS PrimitiveAPI 高级着色入门 - 从参数化几何与 Fabric 材质到着色器 - 下篇
    shell:bash【Bourne-Again SHell】
    前端面试题之HTTP专题
    Linux Tcp 连接 状态 检测 处理
    【校招VIP】“推推”产品项目课程:产品的脑图分析
    CoCube群机器人预览→资讯剧透←
  • 原文地址:https://blog.csdn.net/m0_49968063/article/details/133885787