9.1单独编译
简单说就是将一个函数各个功能分块放在不同文件中,起到整理一下的作用。记住,所有的功能和方法都是为了便于敲代码和易于维护管理所开发。没有程序员会闲的就是好玩去开发新功能。所以新的功能一定要把握一点,就是它为解决什么问题而生,在什么场景下使用。如果这个使用场景你用不到,就可以先放在那里,不要捯饬了。
先上个代码:
#include <iostream>
int add(int a, int b);
int main ()
{
int a = 1;
int b = 2;
std::cout<<add(a, b)<<std::endl;
}
int add(int a, int b)
{
return a + b;
}
OK~非常典型的三段式:函数原型,主函数,函数定义。
好,问题来了,如果我有一万个函数原型,主函数有一万行,函数定义也对应有一万个。。。好了,能疯。
自然想到根据上方三段式的分类,将程序分为三部分:
头文件:包含结构声明和使用这些结构的函数原型。
源代码文件:包含与结构有关的函数代码。简单说就是头文件中函数原型的函数定义。
源代码文件:包含调用与结构相关的函数的代码。简单说就是使用头文件中函数的程序,就认为是主程序好了。
好,分完是三个程序:
1、头文件:head.h
//head.h
int add(int a, int b);
2、函数定义文件:head.cpp
//head.cpp
include "head.h"
int add(int a, int b)
{
return a + b;
}
3、主程序:main.cpp
//main.cpp
#include <iostream>
include "head.h"
int main ()
{
int a = 1;
int b = 2;
std::cout<<add(a, b)<<std::endl;
}
OK,分完了,注意三个点:
1、头文件的命名后缀为.h,其他为.cpp
2、无论是函数定义还是主函数,都要将头文件include进去
3、包含头文件时用引号:#include "head.h"
, 而不是尖括号。
用尖括号,编译器将在存储标准头文件的主机系统的文件系统中查找;
用双引号,编译器将首先查找当前的工作目录或源代码目录,若没有,将继续在标准位置查找。
所以自己写的头文件,就用双引号吧。
头文件有个问题就是只能被include一次,所以有时候会误操作导致多次引用而报错。报错本质原因是头文件中包含的各种函数原型在同一程序中只能出现一次,不能出现多次,如果重复包含头文件,就会因多次出现而报错。
OK,机智的程序员们又想出了ifndef和define关键字:
//head.h
#ifndef MYHEAD_ROBIN
#define MYHEAD_ROBIN
int add(int a, int b);
#endif
用这种格式,将头文件包起来就好了。
这里先直观的理解为:如果没有定义MYHEAD_ROBIN,则定义,定义了就直接跳过,直接到#endif这句。
相当于做了下检查,定义了就直接跳过好了。这个东西其实就是后面的名称空间,后面再说。
9.2存储持续性、作用域和链接性
持续性是指变量的存活时长
作用域是指变量的可见范围
链接性是指变量的可用范围
这是三个属性是重要读是依次递减的,也就是说必须要在持续性的基础上讨论作用域(要是都不存在了,哪还有什么作用域)。链接性则就是作用域体现,作用域内可见,可见即可用,可用则有链接性。
先说持续性,C++中有四种持续性:
1、自动存储持续性:函数定义中声明的变量,在其代码块中创建,代码块执行完后,其内存被释放。简单说就是只在代码块运行中存在的变量。有2种此类变量。
2、静态存储持续性:在函数定义外定义的变量和使用static定义的变量。简单说就是整个程序运行中都存在的变量。有3中此类变量。
3、线程存储持续性:一个线程存在它就存在。
4、动态存储持续性:new分配,直到delete释放之间,变量都存在。
默认情况下,在函数中声明的函数参数和变量为自动、局部、无链接性的变量。
OK,从第一个属性开始变,如果想自动变静态呢?
变静态变量说明这个变量在整个程序周期内都存在。
根据声明的位置不同,体现为不同的链接性分为3种:
1、在代码块外声明,为外部链接静态变量。
2、在代码块外声明,并用static限定,为内部链接静态变量。
3、在代码块内声明,并用static限定,为无连接静态变量。
...
int global = 1000;//外部静态变量
static int one_file = 50;//内部静态变量
int main()
{...}
void function1(int n)
{
static int count = 0;//无链接静态变量
}
这里的static有点梗,有一丢丢关键字重载的味道,不过习惯就好,C++什么事都干的出来。。。
变量在代码块内时,static限定的是持续性,表征变量为静态。
变量在代码块外时,static限定的是链接性,表征变量为内部链接。
关于静态变量只说两个点:
1、只要是静态,在程序整个执行期间都存在,只是可见性和链接性的区别。
2、如果没有显式的初始化静态变量,则编译器自动置为0。
上张图吧:
extern关键字。先说一下它咋来的,假如有俩程序,都用到了一个变量a,很明显,把它声明为全局变量(链接性为外部,持续性为静态,作用域为整个文件夹)好了,但是当我们像上方一样分为好几个文件时就有问题了:
//file01.cpp
int a = 10;
//file02.cpp
//直接a?,貌似不对,这是俩程序,这里的a跟file01貌似没关系。。
//换成int a?貌似也不对,相当于在直接在这里定义了一个变量a,跟file01还是没啥关系。。。
//好了,整来看,就是file02跟file01没有建立什么关系导致的。
extern int a;//从外部引入变量a,注意,这里的int必须有,不能少。
cout<<a<<endl;//这里就可以用了。
看起来很简单是不是?
好,如果要用单独编译的形式呢?头文件,定义文件,使用文件分开。。。
记住两个原则:
1、定义只能有一次,声明可以有很多次!无论函数还是变量。
2、头文件中只放声明,不放定义!
head.h:
//head.h
#ifndef CHAPTER9TEST_HEAD_H
#define CHAPTER9TEST_HEAD_H
int add(int a, int b);
extern int global;//头文件中引入
#endif //CHAPTER9TEST_HEAD_H
head.cpp:
//head.cpp
#include "head.h"
int global = 100;//这里定义
int add(int a, int b)
{
return a + b;
}
main.cpp:
#include <iostream>
#include "head.h"
int main()
{
std::cout << add(3, 4) << std::endl;
std::cout<<global<<std::endl;//这里利用
global =101; //发现还可以更改其值
std::cout<<global<<std::endl;
return 0;
}
做法上看,将变量的定义放在定义cpp中,与函数定义一起。头文件中用extern引进此变量,相当于做了个声明,与函数的声明一起。最后,在main函数中就可以利用了。
说一点自己的想法:
以前一直以为程序结构是顺序是:头文件->定义源文件->使用源文件。所以一直感觉在定义源文件和使用源文件中都要include头文件有点别扭。。感觉应该是头文件中的一堆声明作为桥梁,链接定义cpp和使用cpp。头文件中的声明可以被声明多次,也就可以被include多此。定义cpp中,的相关函数和变量的定义,只能定义一次!流程是使用cpp中遇到函数或变量,通过.h的声明去寻找定义,找到定义cpp中的定于后,又通过.h中的声明,将定义返回给使用cpp去使用。简单画张图:
所以,上方的int global定义在head.h中,然后head.h中有extern引进声明。然后在main.cpp中就可以使用了。
关于静态内部变量,只能在单文件中使用,如果跟全局变量重名的话,将自动屏蔽全局变量。
静态无链接变量在函数代码块中,程序只在启动代码块时对其进行初始化,以后再调用函数时,将不会像自动变量那样再次被初始化。
mutable 关键字,用来指出,机构或类变量为const,其某个成员也可以被修改。
struct data
{
int age ;
mutable int time;
}
const data robin = {1, 2};
robin.age++; //错误!
robin.time++; //允许!
再谈const:
默认情况下,全局变量的链接性为外部,这是变量,如果是常量呢?
//head.cpp
const int a = 100;//是否这句就说明它是全局常量了呢?
事实证明并不是,全局变量在加const变为常量后,链接性自动变为内部,也就是自己变为了局部变量。也就是此处的const相当于static const int a = 100;
。
所以,我们上方说的单独编译,如果是变量,就那么搞,如果是常量,因为这种特性,就可以把const int a=100;
这句放在头文件中了,因为每一次include头文件,其实都是引进了一个局部变量,不会跟其他文件中的include冲突。
//head.h
const int a = 100;
//head.cpp
无a的相关内容。。
//main.cpp
cout<<a<<endl;
这样是不是很简单?~
其实也就牵出全局变量的一个常规用法,就是定义个多个文件用的常量,一般不会变的,比如圆周率这种。如果像变量那样搞,写进定义文件中,然后头文件中一个一个extern,很是蛋疼。。。所以,就定义个这个特性,直接写进头文件中就好了。
如果说,我就是希望一个常量为全局变量,就在定义cpp中定义一次,其他地方不改动,然后必须extern,再声明处用extern定义就好了。
//head.cpp
extern const int states =100;
//head.h
extern const int states;
//main.cpp
cout<<states<<endl;
不知道图个啥??。。。
函数也是还有链接性的,同样有单定义规则限制。函数定义出来之后,默认是静态全局的。
如果想让其变为内部函数,可在前面加static。
static int private(int x);
...
static int private(int x)
{...}
这样,函数只在此文件中可见。单定义规则也适用非内联函数,因此对于每个非内联函数,程序只能定义一次,同样,对于全局函数,也意味着多文件中,只有一个文件包含该函数定义,使用他的程序中,都应包含函数声明(也就是函数原型)。
c++允许内联函数定义放在头文件中。包含头文件的每个文件都有此内联函数的定义。
至此,我们知道,头文件中可特例放入的有const常量,和内联函数。其他都必须要符合单定义规则进行操作。
名称空间:
直接上代码:
//head.h
#ifndef CHAPTER9TEST_HEAD_H
#define CHAPTER9TEST_HEAD_H
namespace robin //整个头文件用namespace括起来就好了
{
int add(int a, int b);
extern const int global;
const int a = 1000;
}
#endif //CHAPTER9TEST_HEAD_H
//head.cpp
#include "head.h"
namespace robin //好吧,也是用namespace将整个括起来。。。
{
const int global = 100;
int add(int a, int b)
{
return a + b;
}
}
这里做了些测试,发现其实按照使用名称空间的方式在head.h文件中用,也是可以的,但是瞅着有点膈应。。。
#include "head.h"
const int robin::global = 100;
int robin::add(int a, int b)
{
return a + b;
}
整个样子也,,行。。。但是,好蠢的感觉。。。
还是用那种namespace全括起来的吧。
在使用程序中,就是那三种方式了:使用名称空间前缀、using声明、using编译指令。不多说了。