目录
文章目录
前言
一 单独编译
二 存储的持续性 作用域和链接性
三 存储方案和动态分配
前言
我们讲面向较为底层的体系去学习
单独编译
存储持续性、作用域和链接性
定位(placement) new 运算符,名称空间
一 单独编译
和C语言一样,C++也很鼓励程序员把功能函数单独写到一个.cpp文件里面去,这样就很好进行管理,然后进行编译这些文件,最后会进行链接这些程序,这里就不详细讲底层了,我之前的文章由写到这些内容C进阶 程序的环境和预处理-CSDN博客感兴趣可以去学习一下这些底层的东西,不仅对我们看这些书本很有用,而且对于未来自己对程序和计算机更加的清楚
1 程序的三个部分
• 头文件:包含结构声明和使用这些结构的函数的原型 也就是一些简单的声明和结构体等
• 源代码文件:包含与结构有关的函数的代码 也就是main函数
• 源代码文件:包含调用与结构相关的函数的代码 也就是功能函数
接下来我们对头文件进行详细的介绍2 头文件包含内容
首先头文件可以包含些什么呢?
• 函数原型
• 使用#defme 或 const 定义的符号常量
• 结构声明
• 类声明
• 模板声明
• 内联函数
首先我们对于这些都需要有具体的认知,下面我来提一个问题
为什么头文件里面可以包含内联函数,然而普通函数不可以?
有人就会说,这不是傻子才会这么做吗,但是你包含了确实不出错呀,所以为什么很多人说不可以?因为你如果包含了,你每次include这个头文件的时候,就会导致你函数重新命名了,但是内联函数不一样,你想想,这个内联函数是把代码嵌套进去,所以并不会导致这个错误
在lDE 中,不要将头文件加入到项目列表中,也不要在源代码文件中使用#include 来包含其他源代码文件——这个是这本书下面的一个小注释,博主也不是很懂,因为博主也才只是一个大一的学生,所以还是会有点懵逼,我们先记下来嘛,如果有大佬看到也希望再留言板留言
3 文件的到可执行文件的流程
我们来看这个图,可能会有人说这个是啥呀,怎么看不懂,看不懂,去看看我上面那链接的文章吧,那个里面有
这里就简单的说一下,这个其实就是把这个.cpp文件经过编译的三个操作,然后生成了.o文件,然后进入链接进行链接,链接启用代码,库函数,和.o文件生成可执行文件,这样我们的文件就可以使用了
4 文件的管理在同一个文件中只能将同一个头文件包含一次。 记住这个规则很容易,但很可能在不知情的情况下将头 文件包含多次。 例如,可能使用包含了另外一个头文件的头文件。 有一种标准的 C/C++技术可以避免多次包 含同一个头文件。 它是基于预处理器编译指令#ifndef(即 ifnot defined )的。 下面的代码片段意味着仅当以前 没有使用预处理器编译指令#define 定义名称COORDlN_H_ 时,才处理#ifndef和#endif之间的语句
这个其实也是那篇文章里面有讲
下面我们来看一个代码就好了#ifndef __COOL_H__ #define __COOL_H__void yang(int x); int zhang(int y);#endif
首先我们有这几行代码,然后就是你如果想引用一个头文件的话为了避免引用多次,我们就进行上述就好了
首先#ifndef和#endif其实就是可以类比我们所用的if和else语法,然后就是如果预处理编译器没有#define __COOL_H__的时候就会执行里面的文件,就是我们两个声明,这样就可以避免文件的重复使用
这个命名书上是没有前面两个下滑线的,由于开发中经常有人这么写还很好看,所以作者就这么写了
这一篇文章的实操很简单,但凡我们学完C或者其他语言都会进行一个小项目的开发,所以实操部分就不讲了,有书的可以翻翻书
多个库的链接
C++标准允许每个编译器设计人员以他认为合适的方式实现名称修饰(参见第 8 章的旁注"什么是名 称修饰" ),因此由不同编译器创建的二进制模块(对象代码文件)很可能无法正确地链接。 也就是说,两个编译器将为同一个函数生成不同的修饰名称。 名称的不同将使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配。 在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。 如采有源代码,通常可以用自己的编译器重新编译源代码来消除链接错误
什么意思呢?
我们学习函数探幽部分的时候,如果我们C++对于函数的重载有命名修饰的作用,然后这个每一个编译器都是不同的,这样就会导致我们在计算机处理的时候,对于一个函数命名不同导致不知道该找哪一个,从而会出错
二 存储的持续性 作用域和链接性
1 C++使用四种不同的方式来存储
• 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被放 C++有两种存储持续性为自动的变量
• 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。 C++有3种存储持续性为静态的变量
• 线程存储持续性 (C++11): 当前,多核处理器很常见,这些 CPU 可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread local声明的,则其生命周期与所属的线程一样长。本书不探讨并行编程
• 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序 结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store) 或堆 (heap)2 四种不同的方式小结
以上或许会看的十分的头疼,我们来总结一下
自动存储就是相当于我们局部变量栈的开辟
静态存储就是使用static这个关键字,还有类似于我们的全局变量和const修饰的全局变量,这个const修饰有关于编译器的优化,这里就不详细讨论,想知道的可以去找我以前的文章
线程这里不展开详细介绍,因为本书也不是主要讨论这个
动态存储就是在堆区开辟,这里需要用到new,delete等很多的关键词
作用域和链接
这里主要考虑我们在一个.cpp文件里面有了一个变量,当我们想在另外一个.cpp文件里面使用的话,这个我们会在后面进行讲解怎么去使用
C++变量的作用域有多种。 作用域为局部的变量只在定义它的代码块中可用。代码块是由花括号括起 的一系列语句。例如函数体就是代码块, 但可以在函数体中嵌入其他代码块。
作用域为全局(也叫文件作用域)的变量在定义位置到文件结尾之间都可用。
自动变量的作用域为局部,静态变量的作用域是全局还是局部取决于它是如何被定义的。在函数原型作用域 (function prototype scope) 中使用的名称只在包含参数列表的括号内可用(这就是为什么这些名称是什么以及是否出现都不重要的原因〉。
在类中声明的成员的 作用域为整个类,在名称空间中声明的变量的作用域为整个名称空间(由于名称空间已经引入到C++语言中,因此全局作用域是名称空间作用域的特例)
C++函数的作用域可以是整个类或整个名称空间(包括全局的),但不能是局部的(因为不能在代码块 内定义函数,如果函数的作用域为局部, 则只对它自己是可见的, 因此不能被其他函数调用。这样的函数 将无法运行)。 不同的C++存储方式是通过存储持续性、作用域和链接性来描述的。 下面来看看各种C++存储方式的 这些特征。首先介绍引入名称空间之前的情况, 然后看一看名称空间带来的影响
我们来总结一下
首先定义在函数内部的就是局部变量,这个作用域是在函数内部可见的,但是当我们把这个变量声明为全局变量的时候,是在每一个函数都可以见,我们想要在别的.cpp文件可见就要用extern进行声明,函数原型的括号里面的变量只是在函数括号的内部可见
类和命名空间后面会进行讲解
3 自动存储持续性这个很简单,简单概括就是每个函数里面的变量只在那个函数里面可见,在别的函数里面都是不可见的
我们来理解一下这个自动存储的持续性#include<iostream> using namespace std; void oil(int x);int main() {int texa = 100;int year = 10;cout << " In main texa = " << texa << endl;cout << " texa' address = " << &texa << endl;cout << " In main year = " << year << endl;cout << " year' address = " << &year << endl;oil(texa); }void oil(int x) {int texa = 100;cout << " In oil texa = " << texa << endl;cout << " texa' address = " << &texa << endl;cout << " In oil x = " << x << endl;cout << " x' address = " << &x << endl; }
我们可以很明显的观察到这个值和这个数字的之间的关系首先在不同函数里面的texa的地址是不一样的,所以作用域是不一样的,然后还可以观察到形参和这个texa的地址也不一样,这个很基础不多阐述了
这个自动存储的是就近原则,那个最近就访问哪一个到后面的时候,有很多的概念,这里就不先介绍了,这个知识点要当我们到项目的时候才可以彻底弄明白这个之讲解这里的重点,这些概念很复杂还很sb,我们只需要抓住重点
这里的重点就是怎么在别的.cpp里面使用别的.cpp里面的函数与变量还有头文件我们该怎么进行书写,这些都是与我们到后面学习息息相关的4 外部链接性质
文件1
#include"cool1.h"double warming = 0.1;int main(void) {cout << " 全局变量warming的值 = " << warming << endl;update(0.1);local();return 0; }
文件2
#include "cool1.h"void update(double x) {warming += x;cout << " update = " << warming << endl; }void local() {double waring = 100.1;cout << " 局部变量里waring值 = " << waring << endl; }
头文件
#ifndef __COOL_H__ #define __COOL_H__#include<iostream> using namespace std;extern double warming;void update(double x); void local();#endif
这样就可以弄成一个回路了
但是我们想在local函数里面输出全局变量怎么办,可以在前面加一个作用域运算符,这样就可以实现输出全局变量了#include "cool1.h"void update(double x) {warming += x;cout << " update = " << warming << endl; }void local() {double warming = 100.1;cout << " 局部变量里waring值 = " << warming << endl;cout << " 全部变量里waring值 = " << ::warming << endl; }
这样就好了,可以实现这里实现全局变量了
那么我们学了这些,什么时候定义全局变量,什么时候定义局部变量
既然可以选择使用全局交量或局部变量,那么到底应使用哪种呢?
首先,全局变量很有吸引力一一因为所有 的函数能访问全局变量,因此不用传递参数, 但易于访问的代价很大一一程序不可靠
计算经验表明,程序越能避免对数据进行不必要的访问,就越能保持数据的完整性。 通常情下,应使用局部变量,应在需要知晓时才传递数据,而不应不加区分地役用全局变量来使数据可用5 内部链接性质
当我们在两个程序里面同时定义一个同样名字的全局变量的话,那么就会导致命名错误,因为都是外部链接属性
我们可以看到程序会爆出这个错误,就是重定义,我们都知道,在链接的时候会产生一个符号表,这个时候我定义的全局变量是会放到这个符号表的,所以编译器去根据这个符号表进行查找,你这样就会导致编译器分不清用哪一个,这个时候就会有内部链接属性了
这个时候我们就可以使用static这个修饰词来修饰这个变量,这样就可以让他变成内部链接属性,就不会导致有这种重名的出现,内部链接属性就是把这个全局变量只可以在当前的文件可见,在别的文件是不可见的,我们要熟练这个方法
这本书介绍了大量的概念知识,这个可以等到我们后续开发项目的时候再来学印象更加深刻6 静态存储性,无链接性
首先这个就是我们在别的函数里面定义了一个变量,当我们函数调用完毕的话,那么就会导致返回,在返回的时候,就会把变量进行销毁,这个时候我们不想让变量进行销毁就要用static进行修饰,这样就会把这个变量放入到常量区,但是这个变量只可以初始化一次,之后就不可以初始化了,下面我们来看一个例子
#include"cool1.h"const int N = 10; void strcount(char* str);int main(void) {char str[N];char next;cout << " Enter your str input computer : " << endl;cin.get(str, N);while (cin) {cin.get(next);while (next != '\n') {cin.get(next);}strcount(str);cout << " Enter your str input computer : " << endl;cin.get(str, N);}cout << "bye bye\n";return 0; }void strcount(char* str) {static int total = 0;int count = 0;while (*(str++))count++;total += count;cout << "the str's long is " << count << endl;cout << "the total long is " << total << endl; }
我们用了一个static修饰一个变量,我们输出结果为这个
从这个图可以看到这个static是一直有保存上一次的值的,但是有人会不理解这个程序,我们来分析一下这个程序
也就是你输入一行没有超过的10的字符串的话,这个cin.get(input,N)也会读取到\n的时候,会终止,把换行保留到缓存区里面,然后后面会有这个cin.get(next)来进行读取,然后吞掉这个换行,然后我如果不想进行程序了,我输入换行会被cin.get(input,N)读取,这个时候没有有效字符,这样就会导致状态设置为为准备好eofbit 或 failbit 被设置,这个时候while里面会为false退出循环
这里的状态我们可以进行学习一下1.
eofbit
(End-of-File标志)
含义:表示输入流已经到达文件结束(EOF)。
触发条件:
当输入流中没有更多数据可读取时,例如用户在终端输入了文件结束符(
Ctrl+Z
在Windows上,Ctrl+D
在Unix/Linux上)。当
cin.get()
或cin.read()
等函数读取到文件结束符时,eofbit
会被设置。影响:一旦
eofbit
被设置,输入流的状态变为“未准备好”,后续的输入操作会失败。2.
failbit
(失败标志)
含义:表示输入操作失败,但不一定是因为到达文件结束。
触发条件:
当输入流中没有足够的数据来满足当前的读取操作时。例如:
使用
cin.get(str, N)
时,如果输入流中没有足够的字符来填充str
(例如输入了一个空行),failbit
会被设置。使用
cin >>
格式化输入时,如果输入的数据格式不符合预期(如将字符串输入到整数变量中),failbit
会被设置。影响:一旦
failbit
被设置,输入流的状态变为“未准备好”,后续的输入操作会失败。3.
badbit
(错误标志)
含义:表示输入流发生了严重错误,通常是不可恢复的。
触发条件:
当输入流被破坏,或者发生了无法恢复的错误时(如文件无法打开、内存分配失败等)。
影响:一旦
badbit
被设置,输入流的状态变为“未准备好”,并且通常无法继续使用。4.
goodbit
(正常标志)
含义:表示输入流状态正常,没有错误。
触发条件:当输入流状态正常时,
goodbit
被设置。影响:只有当
goodbit
被设置时,输入流才被认为是“准备好”的。我们来总结一下,这个输入有四种状态
1 正常标志 2 错误标志 3 失败标志 4 已读取完结束标志
这里我们只需要知道有这四个标志,至于有些检查这些状态的函数就不需要掌握很少用到,当然有一个clear函数,这个是把这个状态清楚,变成正常标志,可以进行正常的输入7 cv-限定词
在讲解const的时候,先讲解两个限定词,虽然我们可能不常用,但是如果是走嵌入式的话,会非常常用
1 volatile
讲解这个限定词的时候,我先讲一下编译器的一些优化
首先嵌入式肯定是要有端口进行接受外部的数据的,比如USB接口啥的,这个时候我们电脑肯定是需要接收实时变化的一个数值,我们在计算机是要定义一个变量来接受这个变量的数值,这个过程可能很抽象,如果我们不在前面用这个修饰的话,编译器自己会整一个编译器优化,就是把这个内存里面的变量存入到这个寄存器里面,然后我们每次去拿这个值的时候,由于是拿寄存器里面的值,这寄存器里面值可能是不变的,就会导致出错
所以我们使用这个修饰词有两个方面
防止编译器优化:当变量的值可能被外部因素改变时,编译器可能会错误地将变量的值加载到寄存器中,导致程序使用的值不是最新的
确保数据一致性:通过声明volatile
,可以确保程序始终访问到最新的数据,避免因数据不一致而导致的错误
当一个变量的值被加载到寄存器中,这个值在寄存器中是可以改变的,并且确实是动态的。寄存器存储的值会根据CPU执行的指令而改变。然而,如果变量被外部因素(如硬件设备或多线程中的其他线程)修改,那么寄存器中存储的值可能不再反映这些外部修改,除非程序显式地重新从内存或端口加载该值
寄存器中的值是动态的,可以改变,但是,如果没有适当的同步机制(如volatile
关键字或其他同步手段),寄存器中的值可能不会自动反映外部对变量的修改。这就是为什么在某些情况下,需要使用volatile
关键字来告诉编译器不要对这个变量进行优化,确保每次访问都能获取到最新的值,这个里面需要涉及硬件和线程的问题了,这里就不多讲了,我们先记住
2 mutable
我们来举一个例子来理解这个东西,如下代码struct Node{int data;mutable char name; }
我们有假设定义一个const类型的结构体变量,这个时候,这个成员里面是不可以被修改的,但是我们里面
3 const
const 限定符对默认存储类型稍有影响。在默认情况下全局变量的链 接性为外部的,但const全局变量的链接性为内部的。 也就是说,在C++看来,全局 const定义〈如下述代 码段所示〉就像使用了 static 说明符一样
就是用const修饰的全局变量的链接属性是内部链接,并不是外部链接
C++修改了常量类型的规则,让程序员更轻松。 例如,假设将一组常量放在头文件中,并在同一个程 序的多个文件中使用该头文件。那么,预处理器将头文件的内容包含到每个源文件中后,都有这个变量相同的名字使用,并且不会报错,这个就是const的方便之处
这里也像前面所说的static一样,但是这个值是不可以被修改的,并且定义时就要进行初始化
如果出于某种原因,程序员希望某个常量的链接性为外部的,则可以使用 extem关键字来覆盖默认的 内部链接性extern const int states = 50; // definition with external linkage
这样也可以进行外部链接,在这种情况下,必须在所有使用该常量的文件中使用 extem关键字来声明它。这与常规外部变量不同, 定义常规外部变量时,不必使用 extem 关键字,但在使用该变量的其他文件中必须使用 extem。然而,请 记住,鉴于单个const在多个文件之间共享,因此只有一个文件可对其进行初始化,也就是说你在定义的时候就要加extern,而不是向我们之前一样用extern进行声明
8 函数的链接性和语言的链接性在默认情况下,函数的链接性为外部的,即可以在文件间共享。实际上,可以在函数原型中使用关键字 extern 来指出函数是在另一个文件中定义的,不过这是可选的(要让程序在另一个文件中查找函数,该文件必须作为程序的组成部分被编译,或者是由链接程序搜索的库文件)
还可以使用static来进行修饰,这样就可以只在这个文件里面可见static int private(double x);
9 语言的链接性
我们前面学习函数重载的时候,我们知道C++编译器会把一下函数的名字给定义为另外一个,链接程序要求每个不同的函数都有不同的符号名,比如有一个函数spiff。在C语言中, 一个名称只对应一个函数,因此这很 容易实现。为满足内部需要.,C语言编译器可能将spiff这样的函数名翻译为_spiff。这种方法被称为C语言链接性
但在 C++中,同一个名称可能对应多个函数,必须将这些函数翻译为不同 的符号名称。因此. C++编译器执行名称矫正或名称修饰,为重载函数生成不同的符号名称。 例如,可能将spitf (int5转换为_spotf_i
至于为什么这个C也有这个?
尽管C语言本身不需要名称修饰,但在实际应用中,编译器可能会出于一些原因对函数名进行转换。这种转换通常是由编译器的实现决定的,并且可能因编译器而异
所以我们像把.cpp文件和.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
这样在函数原型里声明这个就好了
这个上面这个是按照C的编译器进行处理
下面两个是按照C++编译器进行处理
三 存储方案和动态分配
动态内存由运算符new和delete控制,而不是由作用域和链接性规则控制。因此,可以在一个函数中分配动态内存,而在另一个函数中将其释放。与自动内存不同,动态内存不是LlFO,其分配和释放顺序要取决于 new 和 delete 在何时以何种方式被使用
例如int* p = new a[100];
这个是在堆区开辟了一个数组长度为100的数组
如果将p的链接性声明为外部的,则文件中位于该声明后面的所有函数都可以使用它。另外,通过在另一个文件中使用下述声明,便可在其中使用该指针: extern float * pfees,就比如这样子的
利用new初始化int *pi = new int (6);
剩下的内容是要在后面进行深入讲解的,这里只需要知道这个new怎么使用,delete怎么使用就好,这里我前面是有讲过的
重点:定位运算符号new
这个是new运算符号的一种特殊形式,这个可以指定地址在里面创建东西p2 = new (bufferl) chaff;
一般定位运算符号后面要带有一个指定位置的
总结
单独编译
程序的三个部分 源文件 头文件
头文件包含的部分 函数原型 预处理 外部链接的变量 结构体 内联函数 模板
文件到可执行文件的流程 预处理 编译 汇编 链接
文件的管理 我们利用#ifndef和#endif来进行处理
存储的持续性 作用域 和 链接性
C++的四个方式 自动存储 静态存储 动态存储 线程
自动存储
局部变量的作用域和证明
外部链接性质
一般用于全局变量在别的地方都可见,最好是用头文件进行包揽,在前面加一个extern就好了
内部链接性质
一般用于全局变量避免出现名字相同导致的报错,在前面加上static就可以保持在当前的文件可见
静态存储 无链接性质
这个表示利用static修饰局部变量的作用
cv限定词
这个里面有volatile这个是用于硬件相关的,这个就是防止编译器优化导致的错误
mutable这个是表示可以修改const的值,一般用于结构体
函数的链接和语言的链接
函数默认一般都是外部链接,自己可以加static来表示内部链接,使用的话一般是要在头文件里面加上extern这样表示让编译器知道有这个函数
语言的链接性
不同编译器编写函数的名字不一样
存储与动态分配
new的初始化和定位new运算符