1 单独编译
和C语言一样,C++鼓励程序组件放在单独文件中,这样有利于功能模块的添加与卸载。
在单独编译中,可以将程序分为三个部分:
(1)头文件:包含结构声明和使用这些结构的函数原型。
(2)源代码文件:包含具体功能实现,以及与结构有关的函数代码。
(3)源代码文件:包含调用与结构有关函数,也就是将具体特定功能实现转换为通用调用接口。
模块单独编译注意事项:链接编译模块时,应该确保所有对象文件或者库都是由同一个编译器生成的。
在头文件中通常包含如下内容:
(1)函数原型
(2)使用define或者const定义的符号常量
(3)结构声明
(4)类声明
(5)模板声明
(6)内联函数
C++头文件管理:
在一个头文件中只能将一个头文件包含一次,如果包含多次,则编译时会报错,比如提示一个结构重复定义等。但是在不知情下很容易造成多次包含,可以使用ifndef来管理。
#ifndef COORDIN_H_
#define COORDIN_H_
//place include file contents here
#endif
编译器首次遇到该文件时,名称COORDIN_H_没有定义,这是编译器将查看#ifndef和#endif之间内容,并且读取定义COORDIN_H_的一行,如果在同一个文件中遇到其他包含coordin.h的代码,则知道COORDIN_H_已经定义,从而调到#endif之后。
2 存储持续性、作用域和链接性
存储持续性:存储持续性指数据保留在内存中的时间。
作用域:描述名称在文件的可见范围。
链接性:描述了名称如何在不同单元之间共享。
存储持续性分类:
1、自动存储持续性:在函数定义中声明的变量,包括函数参数。在执行所在函数时被创建,在函数或代码块执行完时释放。
2、静态存储持续性:函数外定义的变量或关键字static定义变量。在整个程序运行期间都存在。
3、线程存储持续性:如果变量使用thread_local声明,则其声明周期和所属线程一样长。
4、动态存储持续性:使用new分配的。直到使用delete释放。
作用域分类:
1、局部作用域:这类变量只在定义的函数或者代码块中可以使用
2、全局作用域:在定义位置到文件结尾都可以使用。
链接性分类:
1、外部链接性:可在文件间共享。
2、内部链接性:只能在一个文件中共享。
2.1 自动持续性
(1)在函数中声明的函数参数和自动变量的持续性为自动(创建开始,到函数调用结束时一起消失),作用域为局部(函数内部可见,函数外部不可见),没有链接性(函数外部不可见,当然也不能在文件间共享)。
int main(int argc,char** argv){
int teledeli = 5;
{ //websigh allocated
cout << "Hello \n";
int websigh = -2; //websigh scope begins
cout << websigh << ' ' << teledeli << endl;
} //websight expires
cout << teledeli << endl;
} //teledeli expires
(2)在代码块中定义的变量,该变量存在时间和作用域限定在该代码块中。
(3)当包含同名的局部变量和全局变量时,在代码块中局部变量有效,在代码块之外,全局变量有效。
void oil(int x){
using namespace std;
int texas = 5;
cout << "In oil(),texas=" << texas << ",&texas=" << &texas << endl;
cout << "In oil(),x= " << x << ",&x= " << &x << endl;
{
int texas = 113;
cout << "In block,texas = " << texas << ",&texas= " << &texas << endl;
cout << "In block,x = " << x << ",&x= " << &x << endl;
}
cout << "Post-block texas = " << texas << ",&texas= " << &texas << endl;
}
(4)寄存器变量
建议编译器使用CPU寄存器来存储自动变量,这种方法目的在于需要频繁访问的变量,提高访问速度。
register int count_fast // request for a register variable
2.2 静态持续性
静态存储持续性在程序运行期间一直都存在,但是在链接性方面可以分类不同:
(1)外部链接性:定义在文件中,函数外的全局变量,在其他文件可以使用extern引用性。
(2)内部链接性:定义在文件中,函数之外的使用static修饰的变量或者函数,限定当前文件中使用,其他文件中不可以使用。
(3)无链接性:定义在函数(代码块,或者结构体,类)内部的static变量,程序运行时一直存储,但是函数或代码块中(包括结构体,类)不可使用。
int global = 100; //static duration , external linkage
static int one_file = 50; //static duration , internal linkage
int main(int argc,char** argv){
...
}
void funct1(int n){
static int count = 0; //static duration , no linkage
int llama = 0;
...
}
void funct2(int q){
...
}
(4)静态变量0初始化:如果定义的静态变量未被初始化,那么全部自动初始化为0.
(5)5种变量存储方式:
2.3 静态持续性,外部链接性
单定义规则:某个名称的变量只能有一次定义,即使在不同文件中定义同名变量,也是不行的。
为了实现在不同文件之间共享一个同名变量,同时满足单定义规则,C++提供如下两种声明:
(1)定义声明:它会给变量分配存储空间。
(2)引用声明:不给变量分配存储空间,因为它引用已有的变量。
如果要在多个文件中使用同一个变量,则只需要在一个文件中定义,在其他文件中用关键字extern引用即可:
//file1.cpp
extern int cats = 20; //definition because of initialization
int dogs = 22; //also a definition
int fleas ; //also a definition
...
//file2.cpp
//use cats and dogs from file1.cpp
extern int cats; //not definition because they use
extern int dogs; //extern and have no initialization
...
//file99.cpp
//use cats and dogs from file1.cpp
extern int cats;
extern int dogs;
下面代码演示了如下功能:
(1)在不同函数内可以声明同名变量,因为他们有自己独立地址。
(2)自动变量可能隐藏同名的全局变量。
(3)使用extern关键字重新声明以前定义过的外部变量。
(4)使用C++作用域解析运算符访问被隐藏的外部变量。
//external.cpp --- external variables , compile with support.cpp
#include <iostream>
using namespace std;
double warming = 0.3; // warming defined .
void updata(double dt);
void local();
int main(int argc,char** argv){ //使用全局变量
cout << "Global warming is " << warming << "degress.\n";
update(0.1); //call function to change warming
cout << "Global warming is " << warming << "degress.\n";
local(); //call function with local warming
cout << "Global warming is " << warming << "degress.\n";
return 0;
}
//support.cpp ---- use external varibales . compile with external.cpp
#include <iostream>
using std::cout;
extern double warming ; //use warming another file
void updata(double dt);
void local();
void update(double bt){
extern double warming ; //extern 使用全局文件共享的变量warming
warming += dt;
cout << "Updataing global warming to " << warming << "degress.\n";
}
void local(){
//定义一个局部的自动变量warming,在函数内屏蔽全局文件共享的warming
double warming = 0.8;
cout << "Local warming = " << warming << "degress.\n"
cout << "But global warming = " << ::warming << "degress.\n" ;
//使用C++作用域解析符访问全局共享的warming,而不是函数内定义的自动变量warming。
}
使用const防止全局变量被修改:
//第一个const防止字符串被修改,第二个const确保数组中每个指针始终指向它最初指向的字符串。
const char * const months[12]=
{
"January","February","March","April","May",
"June","July","August","September","October",
"November","December"
};
2.4 静态持续性,内部链接性
将static限定符用于文件内全局变量,或者修饰函数,则该变量或函数为内部链接性,只能在当前文件使用。
//以下代码是错误的,违反了单定义规则
//file1.cpp
int errors = 20;
...
//file2.cpp
int errors = 30;
void froobish(){
cout << errors << endl;
....
}
//以下代码不违反单定义规则
//file.cpp
int errors = 20;
....
//file2.cpp
static int errors = 30;
void froobish(){
cout << errors << endl;
....
}
共享数据的方法:
(1)在文件之间共享数据,则使用全局变量定义,其他文件中使用extern来引用访问。
(2)在文件内部共享数据,则使用static定义内部链接性,其他文件不可以使用访问。
(3)名称空间提供另外一种共享数据方法。
//file1.cpp ---variables with external and internal linkage
#include <iostream>
int tom = 3; //external varibale definition
int dick = 30; //external variable definition
static int harry = 300; //static,internal linkage
void remote_access();
int main(){
using namespace std;
cout << "main() reports the following address:" << endl;
cout << "&tom = " << &tom << ",&dick = " << &dick << ",&harry = " << &harry << endl;
remote_access();
return 0;
}
//file2.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 confict with file1.cpp harry
void remote_access(){
cout << "main() reports the following address:" << endl;
cout << "&tom = " << &tom << ",&dick = " << &dick << ",&harry = " << &harry << endl;
}
2.5 静态持续性,无链接性
将static限定符用于在代码块或者函数内部定义的变量,这样将导致局部变量存储持续性为静态的,该变量虽然只能在代码块中使用,但是在代码块运行结束时仍然存在。并且初始化静态局部变量时,只在启动程序时进行一次初始化,之后调用代码块并不会再次初始化。
//static.cpp --- using a static local variable
#include <iostream>
const int ArSize = 10;
void strcount(const char* str);
int main(int argc,char argv[]){
using namespacce std;
char input[ArSize];
char next;
cout << "enter a line:" << endl;
cin.get(input , ArSize);
while(cin){
cin.get(next);
while(next!='\n'){
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;
cout << "\"" << str << "\"" << endl;
while(*str++){
count++;
}
total += count;
cout << count << "Characters\n";
cout << total << "Characters total\n";
}
2.6 存储说明符
下面是C++里存储说明符:
auto , register , static , extern , thread_local , mutable
在同一个声明中不可以使用多个说明符,但是thread_local除外,它可以和static和extern结合使用。
mutable:指明即使结构或者类被修饰为const,但是被mutable修饰的成员也可以被修改。举例:
struct data{
char name[30];
mutable int accesses;
...
};
const data veep = {"Claybounre" , 0 , ...};
strcpy(veep.name , "Joye Jonx"); // not allowed
veep.accesses++; // allowed
2.7 函数和链接性
(1)C++不允许在一个函数定义中定义另外一个函数,因此函数存储性都是静态的。程序执行期间都是存在的。
(2)默认情况下,函数链接性为外部的,可以在文件之间共享。
(3)可以使用static修饰函数为内部链接性的,这样子函数只能在当前文件使用了。且必须同时在函数原型和定义中使用static修饰。
static int private(double x);
...
static int private(double x){
...
}
(4)static修饰函数和static修饰全局变量一样,只在当前文件可见,并且可以在其他文件定义同名的函数,并且静态函数将覆盖外部定义。
(5)C++的内联函数不受单定义规则限制。这意味着内联函数可以放在头文件里,包含头文件的每个文件都有相同定义的内联函数。C++要求同一个函数的内联定义都必须相同。
调用一个函数时C++在哪里查找函数:
如果原型指出为静态的static,则在当前文件查找;否则编译器在所以文件查找;如果找到两个定义,则编译器发出错误;如果在程序文件中没有找到,那么编译器会到链接的库中寻找,如果仍然没有找到,则报错。
C++运行在程序文件中定义链接库中同名的函数,在调用时C++会优先调用程序文件,然后其次调用库中函数。并且这不会出错。
2.8 语言链接性
C语言链接性:gcc编译器在翻译函数名称时可能比较简单,比如spriff翻译为_spiff。
C++语言链接性:因为C++中允许函数重载,这意味着函数名可能相同,但是参数不同,如果按照C语言链接性,那肯定不能正确调用到。C++在翻译时比较复杂了,会考虑到参数以及返回值。比如spiff(int)转换为_spiff_i,而spiff(double,double)则翻译为_spiff_d_d
在编译C代码是如果连接程序库中包含C++代码,如果仍然按照C调用方式,肯定不能正确调用,这是必须告诉编译器如何调用。同理在C++中调用C也是一样。
extern "C" void spiff(int); //use C protocol for name look-up
extern void spoff(int); //use C++ protocol for name look-up
extern "C++" void spaff(int); //use C++ protocol for name look-up
2.9 存储方案和动态分配
编译器使用三块独立的内存:(1)用于静态变量(2)用于自动变量(3)用户动态存储
通常使用new分配的内存,在程序终止之后都会被系统回收,不过在一些不怎么健壮的系统中,请求大型内存块可能导致不会被回收,因此最好就是使用delete来释放new分配的内存。
使用new运算符为基本数据类型分配内存并初始化:
int *pi = new int(6); //分配一个int类型并初始化
double *ppd = new double (99.99); //分配一个double类型并初始化
struct where{
int x;
int y;
int z;
};
where* one = new where {2 , 4 , 6 }; //分配一个结构体对象并初始化
int *arr = new int[4] {1 , 2 , 3 , 4}; //分配一个数组,长度为4,并初始化
where *array = new where[2] {{1,2,3},{4,5,6}}; //分配一个结构体数组,并初始化
new失败:
new可能找不到请求的内存量,在之前,C++让new返回NULL指针,但是现在讲引发std::bad_alloc错误。
new定位运算符:
通常new负责在堆heap中找到一个足以满足要求的内存,new运算符另外一种变体就是定位new运算符。
定位new运算符可以由程序开发人员指定要使用的内存,比如程序员之前已经分配好了内存,然后要分配一个变量时,直接让编译器从之前分配的中分配内存。
#include <new>
struct staff{
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[500];
int main(int argc,char argv[]){
staff *p1 , *p2;
int *p3 , *p4;
p1 = new staff; // place structure in heap
p3 = new int[20]; // place int array in heap
p2 = new (buffer1) staff; // place structure in buffer1
p4 = new (buffer2) int[20]; // place int array in buffer2
}
2.10 cv-限定符
const , volatile
const:指明内存被初始化之后,程序不能再对它进行修改了。
volatile:程序代码没有对内存单元进行修改,其值也可能发生变化。通俗来说,使用volatile则告诉编译器,该变量直接从内存读取,而不做任何优化处理。
关于编译器优化处理:
现有一个需求,不断检测某个GPIO引脚的值,然后判断。在进行开发时,可以将该GPIO引脚映射到一个内存单元上,这样改内存单元将可能是被该硬件引脚GPIO修改的,而不是程序本身。程序连续的是读取该内存单元,但是编译器有可能不是每次都是直接读取内存,而是将该值缓存到寄存器中,而下次直接读取这个缓存的值。这样实际没有达到我们想要的效果。这其实是编译器的一个优化效果,提高执行效率。
使用volatile修饰之后,可以告诉编译器不要进行这样优化,每次都直接从内存读取。
3、名称空间
通过定义一种新的声明区来创建命名空间,这样做的目的是提供一个声明名称的区域。一个名称空间中的名称不会和另外一个名称空间的同名名称发生冲突。
namespace Jack{
double pail;
void fetch();
int pal;
struct Well{...};
}
namespace Jill{
double fetch();
int pal;
...
}
(1)名称空间可以是全局的,也可以位于另外一个名称空间内部,但不能位于代码块里。因此链接性为外部链接性。
(2)任何名称空间中的名称都不会和其他名称空间中名称发生冲突。
(3)名称空间是开放的,可以把名称加入已有的名称空间。
namespace Jill{
char* goose(char * str);
}
(4)访问名称空间中的名称:通过作用域解析运算符。
Jack::pil = 2.3;
Jack:fetch();
(5)using声明和using编译指令
在C++中并不希望每次都使用限定符访问,这样比较麻烦。using声明可以让指定名称可用,using编译指定让整个名称空间可用。
namespace Hill{
double fetch;
....
}
char fetch;
int main(int argc , char argv[]){
using Hill::fetch; //put fetch into local namespace
double fetch; //**Error . Already an local fetch**
cin >> fetch ; //read a value int Hill::fetch
cin >> ::fetch; //read a value into global fetch
}
使用using编译指令以及using声明增加了名称冲突的可能性。
//下面不会出现错误
Jack::pal = 3;
Hill::pal = 5;
//下面的则出现错误
using Hill::pal;
using Jack::pal;
pal = 4;