前言

  • 声明
  • 定义

#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,就能够保证头文件只被编译一次。
#pragma once是编译器相关的,有的编译器支持,有的编译器不支持,具体情况请查看编译器API文档,不过大部分编译器都有这个预处理指令了。
#ifndef,#define,#endif是C/C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式。
#ifdef 和 #define 显然 if 有define这个宏,才会执行里面的内容


正文

一般情况下:声明就是告诉编译器存在这么一个东西,就有点像函数;而定义则是申请了内存,就像用到了变量。
但是并不是绝对的,概念性的东西还是看人为理解。


函数

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using std::cout;
using std::endl;


int main(){
int x = add(1, 2);

return 0;
}
int add(int a, int b){
return a + b;
}

在学c的时候就知道编译的时候是逐条执行,程序运行先从主函数开始,所以当自定义函数写于主函数之后时你去调用,编译器找不到这个函数的声明,他不知道这个函数在哪,函数有几个形参,函数里面要进行什么操作,函数要返回什么值。
解决方法就是要么将自定义函数全写在主函数之上,或者在主函数前或里面声明。

1
2
3
4
5
6
7
8
int add(int a,int b);   

int main(){
//int add(int a,int b);
int x = add(1, 2);

return 0;
}

也就是说int add(int a,int b); 这一句就是函数的声明,那么定义自然就是{}里面的内容

ps:声明的时候可以不写函数形参的变量名~ 反正这个变量名只有在实现的时候才用到。
所以int add(int a,int b); 在声明的时候可以写成int add(int,int);
甚至说声明的时候变量名和定义的时候不一样也没啥太大关系

除了主函数调用其他函数的时候要声明,函数之间的互相调用如果不是正常顺序也要提前声明

1
2
3
4
5
6
7
void abc(){
cout << "hhhhhabc" << endl;
cba();
}
void cba(){
cout << "cbahhhhh" << endl;
}

这样肯定会报错,因为abc在cba之前声明和定义了,解决方法也跟主函数那会说的一样。


变量-extern

我们常说定义一个变量而非声明一个变量
是因为定义变量的时候一般都进行初始化或者赋值操作了,他不是一个模板一样的存在,而是已经实际开辟了空间。

1
2
int a = 100;
//int a; 也不算声明

写在哪里不重要,对于变量而言默认就是定义。拿初值这个东西来判断变量是声明还是定义是不靠谱的,因为变量即使不初始化也会有值,只不过是随机产生的垃圾值罢了
当然作为变量想要去做声明,需要用到extern关键词。

1
2
3
4
5
extern int a;
int main(){
return 0;
}
int a = 100;

但其实extern的本意是在全局的情况下做声明,目的自然是为了让其他函数或者其他文件能发现这个变量。
所以extern没必要放在函数里去用,而函数声明的时候其实自带了extern,不需要手动设定了,也就说函数自带全局特性。


内存分配

已知的有栈区和堆区
那么全局变量也有对应占用的地方
源代码也会有占用的地方

网上的图可能比较高级和详细:

之所以会划分,是因为总不能运行一个程序就把所有内存都给他,那这样一下子就爆内存了。。
显然是挨个映射到各个区,不用就销毁来得自然。

x86的内存那会最高是4g,实际上倒是达不到,然后内核吃了一大部分,剩下的是给用户的。

内存简图没找到合适的,看什么时候有个合适的好理解。


头文件和源文件

在C语言中:.h的是头文件,.c就是源文件
而c++其实引用的标准头文件已经不怎么看到.h了,事实上也没差;源文件也就是.cpp的文件。

1
2
3
4
5
6
7
//hello.c
#include<stdio.h>

int main(){
printf("hello world!\n");
return 0;
}
1
2
3
4
5
6
7
//hello.cpp
#include<iostream>

int main(){
std::cout << "hello world!" << std::endl;
return 0;
}

两种语言引用的标准头文件不同,输入输出自然也不相同,但是c++是C语言的超集,他可以套用c语言的内容,但是反过来C语言不能套用c++;

简单玩一下分文件写法:

1
2
3
4
5
6
7
8
//function.cpp
int Ave(int a, int b){
return (a + b) / 2;
}

int Add(int a, int b){
return a + b;
}
1
2
3
4
5
6
7
8
9
//main.cpp
#include<iostream>
using std::cout;
using std::endl;

int main(){

return 0;
}

创建两个源文件叫啥无所谓,但是记住一个程序只有一个main函数,所以分文件写也要注意不要出现多个main。
其次就是因为是项目文件,编译的时候一起编译了,但是如何在main.cpp使用function的函数呢?
前面提到过函数本身具有全局性,但是要先有一个声明.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using std::cout;
using std::endl;

int Ave(int a, int b);
int Add(int a, int b);

int main(){
cout << Ave(3, 8) << endl;
cout << Add(5, 10) << endl;

return 0;
}

可以看到声明之后确实能用了,但是如果有很多很多函数,在不同文件想使用它们,每次都要声明一堆也很麻烦,这就要归功于inclue了,将这个文件当头文件引用之后就省去了许多麻烦。

创建一个function.h的文件,将函数声明放置其中。

1
2
3
4
5
6
//function.h
#pragma once
int Ave(int a, int b);
int Add(int a, int b);

//除了声明,其实定义也可以直接写在这里。但是为什么少用看后面编译过程~

在main.cpp文件中引用头文件#include"function.h";

“”和<>的区别其实就在于前者是引用非库文件,后者引用的都是库里标准的头文件

插一嘴C语言编译过程的四个步骤:
C语⾔编译过程分成四个步骤:

  1. 由.c⽂件到.i⽂件,这个过程叫预处理
  2. 由.i⽂件到.s⽂件,这个过程叫编译
  3. 由.s⽂件到.o⽂件,这个过程叫汇编
  4. 由.o⽂件到可执⾏⽂件,这个过程叫链接

那么预处理的时候,它会将所有include的头文件或者宏定义替换展开成真正的内容,就比如头文件里面的声明和定义等。
编译事实上就是将高级语言逐步翻译成机器语言的过程,这里先翻译成汇编代码。【高级语言->汇编->机器码(2进制)。】
汇编也就是将汇编代码翻译成之前说的机器码。
链接过程使用链接器将该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件。

也说明了头文件不会主动编译,因为只有它被调用编译之后才会展开。这也就回到上面为什么不推荐头文件直接写定义,当另一个cpp文件也去包含这个头文件,编译的时候展开,会有多个函数发生重复定义现象。
如果能保证不被多次调用倒是能直接定义。

那么哪些适合写在头文件:


static

1
2
3
static void hhh(){

}

由于static的限制,它只能在文件所在的编译单位内使用,不能在其它编译单位内使用。
也就是说写于头文件的静态函数,被其他文件引用后,不会互相访问,包括静态变量。


inline

1
2
3
inline void hhh(){

}

内联就更不用说了,本身结构简单的话就直接被替换了,算是一种老式优化,所以也不存在冲突的问题,但是通过替换简单的代码,无非就是以空间换时间的做法。


结语

有的时候编译器能稍微人性化