编译

编译过程

c++代码的编译过程大致可以分为编译链接两个部分。编译是指将源代码翻译成机器语言,并形成目标文件;链接是指把目标文件与操作系统的启动代码和库文件组织起来形成可执行文件。
编译阶段可以细分为三个步骤:

  1. 预编译

    预编译步骤,编译器执行预处理指令,得到不包含#指令的.i文件,这个过程会拷贝#include包含的文件代码、进行#define宏定义的替换、处理条件编译指令#ifndef#ifdef

  2. 编译

    通过预编译输出的.i文件中,只有常量:数字、字符串、变量的定义,以及c语言的关键字:mainifelseforwhile等。这阶段要做的工作主要是,通过语法分析和词法分析,确定所有指令是否符合规则,之后翻译成汇编代码。

  3. 汇编

    编译步骤是把汇编语言翻译成目标机器指令的过程,生成目标文件(.obj.o等)。目标文件中存放的也就是与源程序等效的目标的机器语言代码。

  4. 链接阶段:

    链接阶段是将有关的目标文件连接起来,如调用了某个库文件中的函数、调用了其他源文件中的函数或常量等,最终生成可执行文件。

c++程序编译过程
C++的编译过程及原理

gcc与g++

首先gccg++GCC中的两个不同的东西。

1
2
3
GCC:GNU Compiler Collection(GUN编译器集合),它可以编译C、C++、Object-C等
gcc是GCC中的GUN C Compiler(C编译器)
g++是GCC中的GUN C++ Compiler(C++编译器)

而只要是GCC支持编译的程序代码,都可以使用gcc命令完成编译,它可以根据文件后缀名判断当前程序所用的编程语言(也可以使用-x显性指定语言类别,例如gcc -xc xxx即指使用编译C语言代码的方式编译文件);而使用g++命令,无论什么文件后缀名,都以C++代码方式编译文件。

对于C++程序而言,g++编译器对代码书写规范更加严格。同时g++可以连接C++标准库中现有的函数或类对象,而单纯的gcc命令无法自动链接这些标准库文件,如果想要用gcc编译C++文件,需手动链接标准库,gcc -xc++ -lstdc++ -shared-libgcc xxx.cpp

gcc和g++是什么,有什么区别?
GCC的gcc和g++区别

GCC的参数

编译时链接库需要分为两类:直接引用和间接引用
直接引用 是被源码直接调用的库;
间接引用 是被调用库所依赖的库;

  1. -lxxx 指定具体库的名称,编译时需要显式指定直接引用的库名称;
  2. -L 指定链接库的位置,编译时需要显式指定直接引用的库位置;
  3. -Wl,-rpath-link 用于编译时指定间接引用的库位置,如何知道间接引用的库文件名称,也可以显示指定每一个库,用-lxxx
    -Wl,-rpath有两个作用:
    • 用于编译时指定间接引用的库位置;
    • 用于运行时指定所有库的位置,作用同于修改环境变量LD_LIBRARY_PATH,且优先度较高;

CMAKE

  1. 工程名

    1
    project(TEST)
  2. 命名变量

    1
    2
    3
    4
    set (INC_DIR /usr/local/include)  #头文件路径
    set (LINK_DIR /usr/local/lib) #库的路径
    set (SRC_LIST main.cpp ) #源文件名称,源文件可以有多个,之间用空格隔开
    set (LIB pthread Ice) #库的名称
  3. 导入头文件,类似-l

    1
    include_directories(${INC_DIR})  //用${} 来引用变量
  4. 导入库文件,类似-L

    1
    link_directories(${LINK_DIR})
  5. 生成可执行文件

    1
    add_ececutable(test ${SRC_LIST})  //生成一个执行文件test,源文件来自SRC_LIST的源文件列表
  6. 要链接的库文件名称

    1
    target_link_libraries(test ${LIB})  //相当于是链接了-lphread -lIce
  7. 向终端输出信息

    1
    message( SATUS "this is binary dir" ${TEST_BINARY_DIR})  //输出工程所在目录
  8. 换个地方保存目标二进制

    1
    2
    SET(EXECUTABLE_OUTPUT_PATH${PROJECT_BINARY_DIR}/bin)    //可执行文件的输出路径为build/bin
    SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) //库的输出路径为build/lib

Linux下cmake使用简介

内存分区

五大内存分区:堆、栈、自由存储区、全局/静态存储区、常量存储区

#include

#include "..."使用自定义的头文件,让系统优先在当前目录中寻找;#include <...>使用标准库头文件,先去系统目录中找。

多文件编程

定义是指将某个符号完整的描述清楚,它是变量还是函数,变量类型以及变量值是多少,函数的参数有哪些以及返回值是什么等等;
声明仅仅告诉编译器该符号的存在,至于该符号的具体的含义,只有等链接的时候才能知道。
因此,基于定义和声明的不同,多文件的编程,可以实现代码的复用。

在头文件中只存放变量或函数的声明,不要放定义。它本身不参与编译,但是会被许多个.cpp文件包含进去得到编译

  • 声明extern int a;以及void f();
  • 定义const对象。因为const对象没有被extern修饰,所以仅在当前文件中可见,即使被包含到多个文件中也不会导致多重定义
  • 定义内联函数
  • 定义类。成员变量只有具体对象被创建时才会被定义,而成员函数在一开始就会被定义,通常是将类的定义放在头文件中,而成员函数的实现放在源文件中;也可以将成员函数的定义写在类的内部。

头文件防止被重复引入的方法

  1. 使用宏定义
  2. 使用#pragma once
  3. 使用_Pragma

C与C++

面向过程与面向对象

面向过程与面向对象是C与C++最大区别,也是本质区别。

  • 面向过程是分析出解决问题的步骤,把步骤一步一步实现,依次调用;
  • 面向对象是把问题分解成各个对象,建立对象描述事物在整个解决问题的步骤中的行为。
  • 面向过程性能比比较高,常用于嵌入式,单片机;但不利于维护、复用和拓展;
  • 面向对象易维护、服用和拓展,同时利用继承、封装、多态等性质可以设计出低耦合的系统;但性能比面向过程低。

编程风格区别

  • include的头文件:c风格的头文件以.h结尾,c++去掉了.h同时加入了namespace;
  • main函数:C中main的缺省返回值为int;C++中不能缺省;
  • 注释风格:C中使用/* ... */;C++中使用//
  • 符号常量:C中使用#define在预编译时将符号翻译为常量;C++中可以使用const关键字;
  • 强制类型转换:C中(typename)value;C++中typename(value)以及static_cast<typename>(value)
  • 指针:C中将指针视为无符号整数;C++将指针视为一种独立的类型

NULL 与nullptr

在C语言中,NULL被定义为(void*)0,而在C++中,NULL被定义为整数0.
nullptr是C++中空指针类型的关键字,可以转换为任意指针类型。

面向过程

结构体

  1. 定义方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef struct student
    {
    int num;
    struct student *next;
    }student;

    struct student
    {
    int num;
    struct student *next;
    };

    第二种定义方法就是定义了一个student结构体;
    第一种定义方法是用typedefstruct student这个结构体类型名字重新定义为student
    在C++中,结构体在定义的时候,typedef关键字是不必要的

    1
    2
    3
    4
    5
    struct student{
    int num;
    };

    student A;
  2. 结构数组

    结构数组就是结构体类型的数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct student{
    char name[8];
    char sex[4];
    int age;
    char addr[40]];
    };

    student classA[40];
    // 访问
    classA[0].name
  3. 结构指针

    结构指针就是指向结构的指针。

    1
    2
    3
    struct string *student;
    // 访问
    student->name

    ->就是->的组合,student->name相当于(*studeng).name的缩写形式。

数组初始化

  • 静态分配
1
2
3
4
5
int a[3];              //只定义,未初始化
int a[3] = {0}; //定义,并将全部元素初始化为0
int a[3] = {1}; //定义,并将第一个元素初始化为1,其余为0
int a[3] = {0,1}; //定义,并将前两个元素初始化为0和1,剩余元素为0
int a[3] = {1, 2, 3}; //定义,并将全部三个元素初始化为1,2,3
  • 动态分配
1
2
int *p = new int[3]    //定义,并全部初始化为0
int *p = new int[3]{1,2} //定义,并初始化前两个元素为1,2

动态分配与静态分配:
静态分配是指分配固定大小的内存,在定义时便固定数组大小的行为,发生在程序编译和链接的时候;
动态分配指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法,可以由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。发生在程序调入和执行的时候。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// C++中通过 new 运算符来实现动态内存分配


// 第一种用法是动态分配固定类型的变量
// T *p = new T; p为T*类型的指针
int *p = new int;
*p =5;

// 释放空间
// 使用new申请的内存空间,一定要使用delete释放,
// 否者,即便程序运行结束,该空间也不会被操作系统回收。

delete p;


// 第二种用法是动态分配任意大小的数组
// T *p = new T[N];

int i;
int *p = new int[i];

// 释放空间
delete[] p;
// 如果使用 delete p; 会造成”内存泄漏“

局部变量与全局变量、extern与static

局部变量是定义在函数局部的变量,仅在该函数区域内可见,在其他函数语句中不可见。只有在定义函数在执行时,局部变量才存在,即开始时被创建,结束时被销毁。如果返回局部变量的指针或引用,则会造成野指针。
全局变量是定义在所有函数之外的任何变量,作用域是程序从变量定义到整个程序结束的部分,可以被所有函数访问。

static用来控制变量的存储方式和可见性
static告诉编译器将变量存储在程序的静态存储区,静态数据成员按定义出现的先后顺序依次初始化,这样可以供所有对象公用。静态数据成员的值对每个对象都是一样的,但它的值可以更新

  • 修饰局部变量:仅在初次运行时初始化;生命周期变长但其作用域没有改变
  • 修饰全局变量:全局变量将仅在该文件中可见
  • 修饰函数:与修饰全局变量类似改变作用域
  • 在面向对象中:修饰的变量属于类变量,修饰的方法属于类方法

extern用于改变变量或函数的作用域

  • 用在声明前说明“此变量/函数是在别处定义的,要在此处引用”
  • 可以在C++中链接C函数extern "C"{...}
  1. extern在源文件和头文件中的使用
    extern用于声明全局变量时,有两种方法:第一种是在头文件中进行声明,在源文件中进行定义,在其他文件中可以直接使用;第二种是在源文件中直接进行声明和定义,在其他文件中再声明。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // a.h
    extern int a;
    // a.cpp
    int a = 5;
    // b.cpp
    直接只用a即可

    // a.cpp
    int a = 5;
    // b.cpp
    extern int a;
    再使用a

sort

对vector容器自定义排序规则

1
2
3
4
5
6
7
8
9
10
11
12
static bool cmp(const Type1& a, const Type2& b){
// 升序
return a < b;

// 降序
return a > b;
}


int main(Type3 c){
sort(c.begin(), c.end(), cmp);
}

左值与右值

左值是指表达式结束后仍然存在的持久化对象;右值是指表达式结束时就不再存在的临时对象。(所有的具名变量或对象都是左值)

左值引用&,右值引用&&

1
2
3
int &&a = 1; // 对一个不具名变量的引用
int b = 1;
int &c = b; // 不能是 &&, 因为b是左值
  • 左值引用,使用T&,只能绑定左值;
  • 右值引用,使用T&&,只能绑定右值;
  • 常量左值,使用const T&$,既可以绑定左值,又可以绑定右值;
  • 已命名的右值引用,编译器会认为是个左值;

const 修饰指针

  1. const 修饰指针 —— 常量指针
    指针指向可以修改,但是指针指向的值不可以修改

    1
    2
    const int * p = &10

  2. const 修饰常量 —— 指针常量
    指针的指向不可以修改,但是指针指向的值可以修改

    1
    int * const p = &10
  3. const既修饰指针又修饰常量
    都不可以修改

    1
    const int * const p = &10

面向对象

Make