赞
踩
这是最近阅读c++ primer plus的学习笔记,第九章内容主要是了解多文件编译的过程,和编译后各文件中变量的生存时间,作用域,以及链接性的内容,分清变量的自动、静态、动态等存储方式、局部变量、全局变量的区别、内部外部链接性如何体现等。此外C++提供了名称空间的方式主要是为了解决大规模程序不同的库模板等标识符冲突的问题。
大型文件通常将代码分成多个文件来编写
结构如下:
头文件主要包含如下内容
不要将函数定义或者变量声明写到头文件中
如果其他两个cpp文件同时包含一个头文件时,那么里面的函数给或者变量会出现重定义的错误
根据文件进行单独编译,将两个源代码文件和新的头文件一起进行编译和连接,
将生成一个可执行程序。
图片暂时屏蔽记得恢复
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y8Afmnvg-1649441272356)()]
预处理器编译指令
注意:在同一个文件中只能将同一个头文件包含一次
但是有可能在不知情的情况下将同一个头文件包含两次,比如包含的头文件A.h和B.h,但是A.h也包含B.h头文件,这样B.h中的内容就会被重复定义,出现错误!所以为了防止这种情况的发生,经常使用#ifndef(即if not define)的方法。
#ifndef COORDIN_H_
#define COORDIN_H_
//place include file contents here
#endif
不同的C++存储方式是通过存储持续性、作用域和链接性来描述的
C++使用四种不同的方式来存储数据,区别在于数据保留在内存中的时间不同
以下几个分类描述的是变量在内存中存储时间的差别
作用域描述了一个变量、函数或结构体等名称或标识符在文件内的多大范围内可见。
作用域分类
静态变量(static关键字修饰的变量)存储在全局区,它在整个程序运行过程中 都是存在的,至于它是全局还是局部的,取决于它是在何处被定义的。
函数定义、函数调用、函数原型声明
函数原型声明时,只需要告诉编译器有几个形参,形参的类型是什么,至于形参叫什么名字,可以不写,编译器不关心
链接性描述了标识符或名称能否在不同翻译单元(一个.cpp文件可以包含多个.h文件,他们合称为一个翻译单元。
链接性
变量的存储方式
存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 代码块内 |
寄存器 | 自动 | 代码块 | 无 | 代码块内,使用关键字register |
静态、无链接 | 静态 | 代码块 | 无 | 代码块内,使用关键字static |
静态、外部链接 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态、内部链接 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
默认情况下的函数参数和局部变量,变量存在栈区(存在时间为自动),作用域为局部,无连接性。
// autoscp.cpp -- illustrating scope of automatic variables #include <iostream> void oil(int x); int main() { using namespace std; // 1. 第一次定义texas自动局部变量 int texas = 31; int year = 2011; cout << "In main(), texas = " << texas << ", &texas = "; cout << &texas << endl; cout << "In main(), year = " << year << ", &year = "; cout << &year << endl; // 2. oil(texas); // 1. 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; //函数内部的texas为局部变量,与main函数内同名的texas相互不影响,占用两个不同的内存单元 //2. 当储程序从main()执行到oil(x)时,新的定义int texas = 5;隐藏了以前的定义int texas = 31; //新定义可见,旧定义暂时不可见 cout << "In oil(), texas = " << texas << ", &texas = "; cout << &texas << endl; cout << "In oil(), x = " << x << ", &x = "; cout << &x << endl; { // start a block //2.1 当程序运行到内部代码块时,新的定义int texas = 5;不可见, // 他被一个更新的定义int texas = 113;所替代 //2.2 但是变量x仍然可见,因为该代码块中没有定义x变量 int texas = 113; cout << "In block, texas = " << texas; cout << ", &texas = " << &texas << endl; cout << "In block, x = " << x << ", &x = "; cout << &x << endl; //当程序离开该代码时,释放最新的定义int texas = 113;使用的内存 } // end a block //2. 之后第二个定义的int texas = 5;再次可见 cout << "Post-block texas = " << texas; cout << ", &texas = " << &texas << endl; } //函数运行结束之后,第二次函数内定义的int texas = 5;被释放, // 1. 第一次在main()定义的int texas = 31;再次可见
代码运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SH8iwZJX-1649441272360)(2022-04-07-15-53-34.png)]
深入理解自动变量和栈
自动变量随函数的开始和结束而增减,常用的方法是留出一段内存,将其视为栈(后进先出),以管理变量的增减
新数据被象征性的放在原有数据的上面,(是在两个不同的内存单元中,而不是在同一个内存单元中)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-baJRbI8v-1649441272361)(2022-04-07-16-24-06.png)]
三种静态持续变量
... int global; //外部静态变量 static int one_file; //内部静态变量 int main() { ... } ... void func1(int n) { static int count = 0; //无链接性静态变量 int llama = 0; ... } ... void func2() { ... }
三者的区别在于:
注意:static的两种用法
static int one_file;
对于代码块外的声明,(本身已经是全局变量了,是静态的,在全局区),这时static表示内部连接性,只能在本文件中使用。
static int count = 0;
对于代码块内的局部变量,static表示的是存储持续性,与自动方式不同,不会在代码块结束被释放。
注意:所有静态变量默认都是初始化为0的
单定义规则
如果在多个文件中使用外部变量,只需在一个文件中进行该变量的定义,但是在其他所有文件中,都必须使用关键字extern声明它。(定义的时候编译器黑变量分配存储空间,声明的时候不分配内存,而是引用已有的变量)
//file1.cpp int cats = 20; //定义全局变量 ... int main() { ... } //file2.cpp extern int cats; //声明外部变量 ... void func1() { extern int cats; //声明外部变量,函数内的修改变cats得值 } void func2() { int cats; //定义与全局变量同名的局部变量,局部变量将隐藏全局变量,cats(自动,作用域为代码块)的改变不会影响file1.cpp中cats(静态,外部,作用域为所有文件均有效)的值。 cout <<::cats; // ::双冒号为作用域解析运算符,放在变量的前面,表示使用变量的全局版本。将输出file1.cpp中的cats的值。 }
(外部静态变量)全局变量可以在多个文件内共享数据区,而内部静态变量可以在同一个文件中的多个函数间共享数据区。
另外,用static关键字修饰可以避免与其他文件中同名的全局变量发生冲突,在本文件内,将隐藏其他文件同名的全局变量,使用内部静态变量,可以避免发生数据冲突。
示例如下
// twofile1.cpp
int tom = 3; //定义一个全局变量(外部静态)
int dick = 30; // 定义全局变量(外部静态)
static int harry = 300; // 定义一个只在本文年内有效的内部静态变量,
// twofile2.cpp
extern int tom;// 声明全局变量,与file.cpp中的tom相同。
static int dick = 10; // 与file.cpp中全局变量dick同名,用static声明,在本文件内隐藏相应的全局变量,避免数据冲突。
int harry = 200; // 定义一个全局变量
在代码块中使用static定义变量时,能够使局部变量的存储时间为静态的,与自动的局部变量不同,不会随着代码块的结束释放内存,它将一直存在知道整个程序结束。
注意:静态局部变量的定义语句只会在函数第一次调用时执行一次,再次调用该函数时,它将不会再次被初始化,而自动变量在每次调用函数时都会被重新初始化
1. 使用new运算符初始化
C++98
//初始化单个变量
int *pi = new int (6); //圆括号里放初始值
double *pd = new double (99.99);
C++11的编译器支持花括号表达
//初始化单值变量
int *pin = new int {6};
double *pd0 = new double {99.99};
//初始化数组
int *ar = new int [4] {2,4,6,7}; //方括号放数组元素个数,花括号里放初始值
//初始化结构体
struct where
{
double x;
double y;
double z;
};
where *one = new where {2.5,5.3,7.2};
4.定位new运算符
#include<new> struct chaff { char dross[20]; int slag; }; char buffer1[50]; char buffer2[500]; int main() { char *p1,*p2; int *p3,*p4; //the reguler forms of new p1 = new chaff; //place structure in heap p3 = new int[20]; //place int array in heap //the forms of placement new p2 = new (butter1) chaff; //place structure in butter1 p4 = new (butter2) int[20];//place int array in butter2 }
示例: regular new and placement new
#include<iostream> #include<new> using namespace std; const int BUF = 512; const int N = 5; char buffer[BUF]; int main(void) { double* pd1, *pd2; cout << "calling new and plcaement new: " << endl; pd1 = new double[N]; //regular new pd2 = new (buffer) double[N]; // placement new for (int i = 0; i < N; i++) { pd2[i] = pd1[i] = 1000 + 20.0 * i; } // buffer是char型,直接打印地址打不出来,应该加(void*)强转一下,就可以输出地址值 cout << "pd1 = " << pd1 << ", buffer = " << (void*)buffer << endl; for (int 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: " << endl; double* pd3, * pd4; pd3 = new double[N]; // new开辟新空间 pd4 = new(buffer) double[N]; //定位new运算符开辟的空间,和pd2是同一个位置 for (int i = 0; i < N; i++) { pd4[i] = pd3[i] = 1000 + 40.0 * i; } for (int 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: " << endl; delete[] pd1; //释放掉pd1 pd1 = new double [N]; //重新分配pd1指向的内存空间 pd2 = new(buffer + N*sizeof(double)) double[N]; //pd2指向buffer偏移5个单元的内存 for (int i = 0; i < N; i++) { pd2[i] = pd1[i] = 1000 + 60.0 * i; } // buffer是char型,直接打印地址打不出来,应该加(void*)强转一下,就可以输出地址值 cout << "pd1 = " << pd1 << ", buffer = " << (void*)buffer << endl; for (int i = 0; i < N; i++) { cout << pd1[i] << " at " << &pd1[i] << "; "; cout << pd2[i] << " at " << &pd2[i] << endl; } delete[] pd1; delete[] pd3; // delete[] pd2; 这个例子不能用delete释放pd2的内存空间,这是因为char buffer[BUF];在函数体外,申请的是一块静态内存,位于全局区 // 而delete只能释放处于堆区的动态内存,数组buffer处于delete的管辖区之外,会报错。 return 0; }
存储说明符
auto 自动变量
register 寄存器变量
static 内部链接性或静态存储持续性
extern 声明外部变量
thread_local 与线程相关
mutable 用来指出即使结构(或类)变量为const,某个特别的成员也可以被修改。
CV-限定符
volatile :将变量声明为volatile,阻止编译器进行某种优化,(这种优化可可能使硬件端口上的数据出现错误)
const:在默认的情况下,全局变量的链接性为外部的,但const全局变量的链接性为内部的,(和加static说明符的情况基本类似)
原因是C++修改了常量类型的规则,例如将一组常量写在头文件中,并在多个头文件中使用了该头文件,那么预处理器将头文件的内容包含到每个源文件中,所有的头文件都将包含这个全局变量的定义,根据单定义的规则,这将出错。但是const将键列性修改为内部,这时每个文件都可以在文件内部使用这一组常量,而不用一个一个去extern,节省了工作量,非常方便,这就是为什么能够将常量定义放在头文件中的原因。
函数的链接性
语言的链接性
在C++中,函数重载时(同一个名称对应多个函数),C++编译器其实是通过增加"_”将其修改为不同的函数名称,这种方法称为C++语言链接(C++ language linkage)。
名称空间的作用:避免命名冲突,使我们可以在上下文中带调用来自不同库的相同符号,而不必一一将他们的名字改成不同的。名称空间可以帮助我们更好的控制名称的作用域。
关于名称空间的基本知识点
namespace jack { double pail; //variable declaration void fetch(); //function prototype int pal //variable declaration struct Well{...}; //structure declaration } namespace Jill { double bucket(double n){...}; //function definition double fetch; // variable declaration int pal; //variable declaration struct Hill{...}; //structure declaration }
namespace A{
int a = 50;
namespace B{
int a = 2;
}
}
int main()
{
cout << B:a <<endl; //访问空间B的变量a
cout << B::C::a <<endl;//访问空间C的变量a
return 0;
}
namespace B{
int b = 0;
}
namespace B{
char c;
}
//两个B会自动的合并成一个空间
namespace{
int b = 0; //就相当于static int b = 0;
}
int main()
{
cout << b << endl;
}
namespace A{
int a = 50;
namespace B{
int a = 2;
}
}
int main()
{
namespace SNS = A::B;
cout << B::C::a <<endl;//访问空间C的变量a
cout << SNS::a <<endl;//访问空间C的变量a ,嵌套比较多时这样写更方便
return 0;
}
作用域解析运算符::是运算符中等级最高的,它有三个作用:
1.全局作用域符
2. 类作用域符
3. 名称空间作用域符
作用域解析运算符::的具体用法如下
#include<iostream> using namespace std; namespace fun1 { int num = 1; //也是定义在全局空间中的,但是由于在名称空间fun中,所以不会和(int num = 2; //全局变量)冲突 double d = 1.99; } namespace fun2 { int num = 5; namespace fun3 { int num = 6; } } int num = 2; //全局变量 int main(void) { int num = 3; //局部变量 { int num = 4; cout << num << endl; //代码块内局部优先,num = 4 } cout << num << endl; // 局部变量, num = 3 cout << ::num << endl; //解析到全局变量,num = 2 cout << fun1::num << endl; //解析到fun1名称空间,num = 1 cout << fun2::num << endl; //解析到fun2名称空间,num = 5 cout << fun2::fun3::num<< endl; //解析到fun2中嵌套的fun3名称空间,num = 6 return 0; }
这两种机制是为了简化名称空间中名称的使用,名称空间里很多东西都用::写起来比较麻烦。
区别:
using 声明
namespace Jill{
double bucket(double n){...}; //function definition
double fetch; //variable declaration
struct Hill{...}; //structure declaration
}
char fetch; // global variable
int main()
{
using Jill::fetch; //put fetch into local namespace
double fetch; //error! Already have a local fetch (using Jill::fetch;)
cin >> fetch; //read a value intto Jill::fetch
cin>> ::fetch; //read a value into global fetch(char fetch;)
}
在代码块内使用using声明,就相当于是使这个名称成为了一个局部变量,那么再次定一个局部变量double fetch;时就会报错,局部变量重定义了,而全局变量char fetch;则由于在代码块内,被局部变量暂时隐藏了。
void other() namespace Jill { double bucket(double n){...} double fetch; struct Hill{...}; } using Jill::fetch; //put fetch into global namespace int main() { cin >> fetch; //read a value into Jill::fetch other(); ... } void other() { cout << fetch; // display Jill::fetch }
在函数外使用using声明时,就使名称成为全局变量
using 编译指令
类似的,在函数体外使用using编译指令,可以是该名称空间的名称全局可用
在函数体内使用using编译指令,使该空间名称在该函数内可用。但是不会与局部变量相冲突,局部变量会隐藏名称空间中重名的变量名称。
#include<iostream> #include<new> using namespace std; namespace fun1 { int num = 1; //也是定义在全局空间中的,但是由于在名称空间fun中,所以不会和(int num = 2; //全局变量)冲突 double d = 1.99; } int num = 2; //全局变量 int main(void) { int num = 3; cout << num << endl; // num = 3 cout << ::num << endl; //num = 2 cout << fun1::num << endl; //num = 1 return 0; }
一般来说,使用using声明更多一些,(更安全),因为有相同的局部变量名会报错,而编译指令方式不会,容易造成混乱。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。