C++允许鼓励程序员将组件函数放在独立的文件中,可以单独编译这些文件,然后把他们链接起来(通常C++编译器既能编译程序,也能管理链接器)。如果一个更改,重新编译这一个,再重新链接。
多文件组织策略
注意:
//pch.h
int a;
//cpp1
#include "pch.h"
void UseA1() { a = 1; }
//cpp2
#include "pch.h"
void UseA2() { a = 1; }
//cpp3
#include "pch.h"
void UseA3() { a = 1; }
报错类似于:
格式:错误 ***** 已经在 ****.obj 中定义
原因:报错的原因就是有三个个CPP,各自生成自己的obj,那么在查找符号的时候,都能发现对方那里也有一个变量a
所以,如果你能保证你的头文件只被包含一次,那么可以在其中在头文件里面定义;或者在一个文件中,使用extern,让其他文件使用
下面式头文件中常包含的内容
#include
#include "myFile"
包含规则:同一个文件只能将同一个文件包含一次
如何避免:基于C++预编译器指令 #ifndef ( if not defined)
#ifndef COORDIN_H_
...
#endif
上面代码意义:当且仅当没有使用预处理编译器指令#define定义的名称 COORDIN_H_时,才处理两个#之间的内容
#ifndef COORDIN_H_
#define COORDIN_H_
//放入你需要包含的文件
...
#endif
通常 #define 语句类似 #define min 0
但只要将#define用于名称,就足以完成该名称的定义
#define COORDIN_H_
上面代码过程:
首先编译器遇到这个文件,发现 COORDIN_H_没有定义,处理两个#之间的内容,并读取 #define COORDIN_H_这一行
(如果同一个文件遇到其他包含coordin.h的代码,编译器就知道COORDIN_H_已经被定义,跳到#endif后面一行)
多个库的链接
C++允许程序员以合适的方式实现名称修饰(解决由于程序实体的名字必须唯一而导致的问题的一种技术),不同编译器创建的二进制模板(对象代码文件)很可能无法正确的链接,因为两个编译器为同一个函数生成不同的修饰名称,无法调用另一个编译器生成的函数定义匹配。
总结:链接编译模块时,请确保所有对象文件或库由同一编译器生成的
分配内存时间:执行变量所属的函数代码块时分配内存
作用域:为其声明的位置,函数结束,变量消失
注:假如一个内部代码块定义了一个与外部代码块名字一样的,当执行到内部代码时,新定义隐藏了以前的定义,也就是使用的是新定义的
在以前的用于显式地指出变量为自动存储,但这个几乎程序员不用,因此赋予新含义现在用于自动类型推断
保留的原因:又要兼容老版本,避免现有代码非法
注:自动推动只能用于单值厨师胡,而不能用初始化列表
建议CPU寄存器来存储自动变量,提高访问速度
以前会提示这个变量用得很多,编译器可以对其做特殊处理。但硬件编译器复杂化,现在这种提示作用失去了,关键字只显式地指出变量为自动存储,
保留的原因:又要兼容老版本,避免现有代码非法
程序整个执行都存在
格式 | 使用 | 数据初始化 / 函数定义 | 属于 | |
---|---|---|---|---|
静态数据成员 | static int a | 类名: :a / 对象名 . a | 类外初始化 | 类的一部分 |
静态函数成员 | static int a() | 类名: :a() / 对象名 . a() | 类内外都可以定义 | 类的一部分 |
普通数据成员 | int a | 非静态数据成员必须与特定对象相对 | 类内外都可以初始化 | 对象一部分 |
普通函数成员 | int a() | 非静态函数成员必须与特定对象相对 | 类内外都可以定义 | 对象一部分 |
为了能扩展本文件作用域或者在其他文件中可以使用
1.extern 作用于外部变量与函数原型(函数本身就是外部)
2.告诉编译器这个全局变量或函数定义在本文件或者文件中定义
在定义全局变量或函数的文件使用extern | 在非定义全局变量或函数的其他文件使用extern | |
---|---|---|
extern +全局变量 | 扩展变量的作用域 由【定义到本文件结束】变成【 声明到本文件结束】 | 扩展该变量的作用域到本文件 那么这个文件中的生效范围【从声明到本文件结束】 |
extern+函数原型=函数原型 | 扩展该函数作用域 由【定义到本文件结束】变成【 声明到本文件结束】 | 扩展该函数的作用域到本文件 那么这个文件中的生效范围【从声明到本文件结束】 |
1.首先检测本文件中有没有这个外部变量或者函数,有就extern本行开始作用
2.没有就去其他文件找外部变量或者函数,有就扩展到本文件extern本行开始
1.一个文件中主要是为了扩展作用域,比如你定义的外部变量在一个滞后的位置,但你想在定义的前面用,就是前面做声明就能合法使用,函数同理
2.多个文件中也是为了扩展作用域到本文件,多文件用同一个名字的外部变量,不能分别定义;程序链接时会出现 重复定义的错误
如上面的讲述不难得到,在定义全局变量函数的时候 +static (静态外部变量,函数),只能在本文件中使用,其他文件不许使用
最后一句:const全局变量的链接性为内部,这里与使用static一致
链接性 | 可访问的 | 定义 | 需求关键字 |
---|---|---|---|
无链接性 | 当前函数访问 | 函数内 | 无 |
内部链接性 | 只能在当前文件访问 | 函数外 | 使用static |
外部链接性 | 其他文件可访问 | 函数外 | 其他文件访问,使用extern关键字 |
这是由于我删除了pch.cpp的原因
函数自动为静态,外部链接性
要设置为内部链接 static+原型和定义
static 本文件查找
非static 所有文件查找(找到两个相同的定义 报错)
然后库里面找 (库里面找到一样的,使用程序员手写的那个)
new 操作符是由语言内建的,就像sizeof那样,不能被改变意义,做相同的事情,下面两件
void *operator new(size_t size)
直接调用 operator new 返回指针,指向一块足够容纳对象的内存
int *p=operator new(sizeof(int));
string *a = new string(''666'');
void *memory //取得原始内存
=operator new(sizeof(string)) //放置string对象
call string::string("666") //调用constructor 对象初始化
on *memory;
string *ps=static_cast<string*>(memory);
为什么我们不直接使用指针来操作空间,不需要new?
你没有申请,这个是不允许的
常规变量找地址: 如 a 运用地址运算符 &a就能知道地址,使用常规变量,值是指定的量,而地址为派生量
指针变量找地址:指针名是地址,*运算被称为间接值或解除引用,将其运用于指针,可以得到该地址处存储的值(C++根据上下文来确定是乘法还是解除引用),地址是指定的量,而值为派生量
C++创建指针时计算机给指针的是用来存储地址的内存,而不是分配地址指向数据的内存,指针指向不同长度的类型时,一般来说要2字节或4字节,根据系统来定
void Init(int* &ht1)
{
ht1 = (int* )malloc(sizeof(int));
}
void InitNoR(int* ht1)
{
ht1 = (int*)malloc(sizeof(int));
}
int main()
{
int* ht;
Init(ht);
}
优先级*& 同级结合结合方向从右到左,&先结合,是个引用 int* &ht 也就是int* 型的引用 int* 是指向int的指针,连起来就是指向 int的指针的引用
引用必须声明时将其初始化,而不能像指针那样,先声明再赋值
int &a=b int* const c=&b a与c的作用相同
在C中关于引用的性质:如果程序中声明b是a的引用,实际上是为b开辟了一个指针型的存储单元,其中存放变量a的地址,输出b时,就输出b所指向变量a的值,相当于 *b 。引用的实质是一个 指针常量 (比如 const int *p)不能改变指针指向不能改变里面存储的内存地址
也就是说
对于int* &ht1 是两个相关联的 对于ht1 进行mallco就是对ht mallco
对于int* ht1 是两个不相关联的 对于ht1 进行mallco,并没有对ht mallco,所以会报错说,使用未初始化的指针
一定要在应用之前,将指针初始化为一个确定的,适当的地址(这是关于使用指针的金科玉律)
虽然地址只是数字,并没有提供类型或长度,只指出了对象存储地址开始的地方,
但我们通过 int * a=new int; 知道 * a是4个字节int,cout的时候就知道怎么读取了
int A=3;
int *nA=new int;
*nA=3;
// nA是用来访问的指针
// new int new找到一个正确的内存块,返回该内存地址给指针变量
//创建动态数组
int i;
int* a = new int[5] {1,2,3,4,5};
//必须在类型名后加上方括号,其中包含元素数目,意思就是创建5个int大小的数组,用指针变量a来访问,并指向第一个
//使用动态数组两种方法
1.
for (i = 0; i < 5; i++)
cout << a[i] << " ";
2.
for (i = 0; i < 5; i++)
{
cout << a[0]<<" ";
a++;
}
delete[] a;
注:动态的数组 用列表初始化;一个使用圆括号
int* a = new int[5] {1,2,3,4,5};
int *a = new int (1);
编译器不能跟踪new出来的数组分配情况,所以必须让运行时程序来跟踪,的确,程序确实跟踪了,以便 delete [ ] 运算符能够正确地释放这些内存,但信息不能用,所以sizeof无法来确定动态分配地数组包含地字节数
int* a = new int[i*j];
cout<<*(a+m*j+n); //相当于输出 a[m][n]
称为定位new运算符,让你能够指定要使用的位置,使用这种特性来设置其内存管理规程,处理需要通过特定地址进行访问的硬件或在特定位置创建对象
注:其实这里调用的是特殊版本的 operator new 称为 placement new 为了在以及分配好的内存上面,构造对象
void * operator new(size_t,void *location)
{
return location;
}
使用
char a;
int *p1;
p1=new(&a) int[20]; // 从 a 变量的地址开始,使用20个int类型的空间
() 括号里面需要地址参数
注:
int *p2;
p2=new (&a) int[20]; //这样会覆盖上面代码写入的内容
delete只能用于常规new运算符分配的空间,所以定位new运算符不需要delete取出来,但是假如上述的 char a 是new分配的,可以使用常规的delete来释放
对于类里面的定位new运算符来为对象分配空间,必须保证他的析构函数被调用,如何调用?显式调用析构函数
产生于heap , 要调用constructor 使用new ,不要调用constructor 使用operator new,如果想要自己写一个内存分配方式,自己写一个 operator new
产生于已分配的 ,调用 placement new