C, C++, Cmake的相关入门学习笔记,项目地址:C-coding
C
1 | system("read -p 'Press Enter to continue...' var");//linux按任意键继续命令 |
C++
1.绪论
C++最初由 Bjarne Stroustroup 于 1979 年在贝尔实验室开发,旨在作为 C 语言的继任者。但不同于C 语言,C++是一种面向对象的语言,实现了继承、抽象、多态和封装等概念。
C++是一种中级编程语言,这意味着使用它既可以高级编程方式编写应用程序,又可以低级编程方式编写与硬件紧密协作的库。
构建可执行文件:编写代码(.cpp) > 编译器(.o / .obj) > 链接器(.exe)
1 |
|
开发环境:Visual studio code + GCC compiler/MinGW, 按F5 Choose C/C++: g++.exe build and debug active file
, 将编译、链接并执行应用程序
2.C++程序的组成部分
- 预处理器编译指令
#include
#include "...relative path to .\FileB"
包含自定义头文件,<>
用来包含自定义头文件 - 程序主体
main()
程序的起点,前面的int是一种标准化约定,表示返回类型为整数 - 返回值
在 C++中,除非明确声明了不返回值,否则函数必须返回一个值,根据约定,程序员在程序运行成功时返回 0,并在出现错误时返回−1
namespace名称空间:是给代码指定的名称,有助于降低命名冲突的风险,如std::cout
:调用名称空间 std 中独一无二的 cout, 若要省略std::, 先加入using namespace std
注释:
1 | //单行注释 |
cin 可用于从用户那里获取文本输入和数字输入std::cin >> Variable1 >> Variable2;
3.使用变量和常量
变量
VariableType VariableName = InitialValue;
变量类型向编译器指出了变量可存储的数据的性质,编译器将为变量预留必要的空间。变量名由程序员选择,它替代了变量值在内存中的存储地址;
函数内部声明的为局部变量,作用域为局部,被限定在声明它的函数内,函数结束后,将销毁所有局部变量,并归还它们占用的内存;在函数外部声明的则为全局变量。
命名约定:对于变量名,采用骆驼拼写法(firstNumber, 第一个单词的首字母采用小写),而对于诸如函数名等其他元素,采用 Pascal 拼写法(MultiplyNumbers(), 函数名每个首字母都大写)。
编译器支持的常见 C++变量类型:
类型 | 值 | 概念 |
---|---|---|
bool | true/false | 布尔变量 |
char | 256个字符值 | 存储单字符,如’A’ |
unsigned short int | 0~65535 | 占16位内存=$2^{16}$=65536 |
short int | –32768~32767 | 最高有效位(MSB)做符号位 |
unsigned long int | 0~4294967295 | $2^{32}$ |
long int | –2147483648~2147483647 | |
int (16位) | –32768~32767 | |
int (32位) | –2147483648~2147483647 | |
unsigned int(16位) | 0~65535 | |
unsigned int(32位) | 0~4294967295 | |
float | 1.2e–38~3.4e38 | 浮点数 |
double | 2.2e–308~1.8e308 | 双精度浮点数 |
sizeof 确定变量长度(字节):sizeof (int)
使用列表初始化避免缩窄转换错误:int anotherNum{ largeNum };
关键字 auto 自动推断类型:auto coinFlippedHeads = true
typedef 替换变量类型: typedef unsigned int MYINT;
常量
在 C++中,常量类似于变量,只是不能修改。
- 字面常量:可以是任何类型:布尔型、整型、字符串等
- 使用关键字 const 将变量声明为常量(最实用):
const double pi = 22.0 / 7;
- 使用 constexpr 定义常量表达式:
constexpr double GetPi() {return 22.0 / 7;}
- 使用关键字 enum 声明枚举: 指定一组特定的取值,枚举量起始值默认为0
使用#define 定义常量:#define pi 3.14286
, 已被摒弃
务必确保变量名阐述了变量的用途。
务必对变量进行初始化,确保变量包含非随机的确定值;并使用列表初始化来避免缩窄转换错误。
不要将保留的 C++关键字用作变量名,因为这将导致程序无法通过编译。
4.管理数组和字符串
数组
在 C++中,数组让您能够按顺序将一系列相同类型的数据存储到内存中。
静态数组:
1 | int myNumbers [5] = {}; //声明一个包含 5 个 int 元素的数组,并将每个元素都初始化为零 |
动态数组 std::vector
:
1 |
|
字符串
C 风格字符串(危险):
1 | std::cout << "Hello World"; |
C++字符串:使用 std::string
:
使用 C++标准字符串是更高效、更安全的方式。不同于字符数组(C 风格字符串实现),std::string 是动态的
1 |
|
5.使用表达式、语句和运算符
从本质上说,程序是一组按顺序执行的命令。这些命令为表达式和语句,使用运算符执行特定的计算或操作。
- 语句:分号界定了语句的边界; 要将一条语句放到两行中,可在第一行末尾添加反斜杠
\
; 可使用花括号({})将多条语句组合在一起,以创建复合语句(语句块) - 运算符:
=
赋值运算符,左值通常是内存单元,右值可以是内存单元的内容。+ - * / %
求模运算符%返回除法运算的余数,只能用于整数++ --
递增和递减运算符,分为前缀与后缀:1
2int num = ++num; //前缀,先+再赋值
int num = num++; //后缀,先赋值再+== !=
相等性检查的结果为布尔值,即 true 或 false, 1 / 0< > <= >=
- 逻辑运算符(返回布尔值):
1
2
3
4! //NOT 用于单个操作数,用于反转
&& //AND, 2true则true
|| //OR, 1true就true
^ //XOR异或,1true才true - 按位运算符(返回运算结果):
~ & | ^
- 移位运算符,用途之一是将数据乘以或除以 $2^n$
1
2int halfNum = inputNum >> 1;
int quadrupleNum = inputNum << 2; - 复合赋值运算符,将运算结果赋给左边的操作数
num1 += num2;
- 运算符
sizeof
, 确定变量占用的内存量
运算符优先级,C++标准非常严格地指定了各种运算的执行顺序:int myNumber = 10 * 30 + 20 – 5 * 5 << 2;
应写作 int myNumber = ((10 * 30) – (5 * 5) + 20) << 2;
,使用括号让代码和表达式易于理解
6.控制程序流程
条件执行
if...else
条件不为0就被视为trueif...else if...else
switch-case
条件处理,相比if-else-if结构化程度更高
1 | switch(expression) |
?:
条件运算符/三目运算符, 相当于紧凑的 if-else 结构(conditional expression evaluated to bool) ? expression1 if true : expression2 if false;
循环执行
goto
(避免使用 goto,可防止代码不直观且难以维护。)
1 | SomeFunction() |
while
只要条件为 true,就将反复执行该语句块
1 | while(expression) |
do...while
循环逻辑至少执行一次时
1 | do |
for
1 | for (初始化语句 executed only once; //迭代器 |
C++11引入了基于范围的 for 循环,让对一系列值(如数组包含的值)进行操作的代码更容易编写和理解。
1 | char charArray[] = { 'h', 'e', 'l', 'l', 'o' }; |
continue
能够跳转到循环开头,跳过循环块中后面的代码;break
退出循环块,即结束当前循环。
控制无限循环
1 | for (;;) // no condition supplied = unending for |
7.使用函数组织代码
函数让您能够划分和组织程序的执行逻辑。通过使用函数,可将应用程序的内容划分成依次调用的逻辑块。
- 声明函数原型
double Area(double radius)
= 返回值类型 函数名(函数接受的参数列表)
函数可接受用逗号分隔的多个参数,但只能有一种返回类型。可以给多个参数指定默认值,但这些参数必须位于参数列表的末尾。 - 定义函数
函数定义由一个语句块组成。除非返回类型被声明为 void,否则函数必须包含一条 return 语句; - 调用函数
如果函数声明中包含形参(parameter),调用函数时必须提供实参(argument)
递归函数:调用自己,必须有明确的退出条件
多条 return 语句的函数:可使用 return 语句退出
函数数据处理
函数重载:名称和返回类型相同,但参数不同的函数被称为重载函数。
1 | double Area(double radius); // for circle |
数组传递给函数: void DisplayIntegers(int[] numbers, int Length);
&
按引用传递参数, 让函数修改的变量在其外部(如调用函数)中也可用(详见第八章引用):
1 | void Area(double radius, double &result) |
微处理器如何处理函数调用
函数调用 在微处理器中的过程:跳转到属于被调用函数的下一条指令处执行。执行完函数的指令后,返回到最初离开的地方;
因此,编译器将函数调用转换为一条供微处理器执行的 CALL
指令, 指出接下来要获取的指令所在的地址,该地址归函数所有。遇到 CALL
指令时,微处理器将调用函数后将要执行的指令的位置保存到 栈 中,再跳转到 CALL
指令包含的内存单元处。
栈是一种后进先出的内存结构,将数据加入栈被称为压入操作, 从栈中取出数据被称为弹出操作。栈增大时,栈指针将不断递增,始终指向栈顶;
栈的性质使其非常适合用于处理函数调用。函数被调用时,所有局部变量都在栈中实例化,即被压入栈中。函数执行完毕时,这些局部变量都从栈中弹出,栈指针返回到原来的地方。
如:微处理器执行CALL指令指出的内存单元包含属于函数的指令,直到 RET 语句(return 语句对应的微处理器代码)导致微处理器从栈中弹出执行 CALL 指令时存储的地址。该地址包含调用函数中接下来要执行的语句的位置
内联函数
使用关键字 inline 发出请求,要求在函数被调用时就地展开它们:inline double GetPi()
编译器通常将该关键字视为请求,请求将函数 GetPi()的内容直接放到调用它的地方,以提高代码的执行速度(因为执行函数调用的开销可能非常高),仅当函数非常简单,需要降低其开销时,才应使用该关键字
(根据性能设置,大多数较新的编译器都能判断应内联哪些函数,进而为程序员这样做)
自动推断返回类型: auto Area(double radius)
lambda 函数: [optional parameters](parameter list){ statements; }
lambda函数是 C++11 引入的,有助于使用 STL 算法对数据进行排序或处理,可以在需要函数对象的地方使用,用于简化代码和提高可读性。
8.阐述指针和引用
C++最大的优点之一是,既可使用它来编写不依赖于机器的高级应用程序,又可使用它来编写与硬件紧密协作的应用程序。能够在字节和比特级调整应用程序的性能。要编写高效地利用系统资源的程序,理解指针和引用是必不可少的一步。
指针 *
指针就是地址,是存储内存地址的变量,是一种指向内存单元的特殊变量。
(内存单元地址通常使用十六进制表示法)
1 | int * pointsToInt = NULL; //空指针,声明指针并初始化,务必初始化指针变量,否则它将包含垃圾值。空指针无法访问 |
动态内存分配 new delete
静态数组的长度是固定的,不能根据应用程序的需求增大或缩小, 因此使用 new 和 delete 动态地分配和释放内存
1 | int * pointToAnInt = new int; //给整型分配内存(int* Pointer = new int[10]; 为一系列元素分配内存 |
运算符 new 和 delete 分配和释放自由存储区中的内存。自由存储区是一种内存抽象,表现为一个内存池,应用程序可分配(预留)和释放其中的内存。
将关键字 const 用于指针
1 | int Age=23; |
数组变量是指向第一个元素的指针, 类似于在固定内存范围内发挥作用的指针,因此也可将用于指针的解除引用运算符(*)用于数组
1 | int arr[10]={1,2,3}; |
指针和函数,地址传递可以修饰实参,值传递不改变实参
1 | //以交换数字为例 |
使用指针相关错误
- 内存泄漏:new动态分配的内存没有用delete释放
- 无效指针(野指针):务必确保指针指向了有效的内存单元, 否则使用 * 和 delete 时会崩溃
- 悬浮指针:使用 delete 释放后,任何有效指针都将无效,很多程序员在初始化指针或释放指针后将其设置为 NULL,并在使用运算符 * 对指针解除引用前检查它是否有效(将其与 NULL 比较)
- new内存分配失败:大块内存分配请求不一定能成功,失败会引发
std::bad_alloc
异常并中断执行
(try-catch
异常处理结构让程序能够向用户指出这一点,再正常退出;或可使用 new 变种new(nothrow)
,在内存分配失败时不引发异常,而返回 NULL,让您能够在使用指针前检查其有效性)
引用 &
作用:给变量起别名
语法:数据类型 &别名 = 原名
引用运算符(&), 也叫地址运算符,用来获取变量的地址。
引用是变量的别名,只是另一种访问相应变量存储的数据的方式。直接调用,避免将形参复制给形参,减少复制步骤的开销,极大地提高性能
1 | int original = 20; |
引用做函数参数:
1 | //3.引用传递, 形参会修饰实参,且比用指针地址传递简单 |
引用做函数返回值:
1 | int& test(){ |
引用的本质是一个指针常量:
1 | int& ref=a; //内部: int* const ref=&a; |
9.类和对象
现在开始面向对象
类和对象
将一系列数据和函数整合在一起的结构就是类,让您能够创建自己的数据类型,并在其中封装属性和使用它们的函数。
(封装指的是将数据以及使用它们的函数进行逻辑编组,这是面向对象编程的重要特征)
1 | //声明类, 使用关键字class |
在程序执行阶段,对象是类的化身。要使用类的功能,通常需要创建其实例—对象,并通过对象访问成员方法和属性。
1 | //创建 Human 的对象 |
句点运算符 (.) 用于访问对象的属性
1
2
3
4
5Man.age= "23";
Man.Talk();
Human* Woman = new Human();
(*Woman).Talk(); //使用解引用指针操作符 *,将指针 Woman 指向的地址解引用,获取指向的 Human 对象。指针运算符(->)访问成员
1
2
3
4Human* Woman = new Human();
Woman->age = "22";
Woman->Talk(); // 更简洁的写法,直接通过指针调用成员函数
delete Woman;例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using namespace std;
class Human
{
public:
string name;
int age;
void Talk()
{ cout << "I am " + name <<", " << age << " years old" << endl;
}
};
int main()
{
Human Man;
Man.name = "wyj";
Man.age = 23;
Human Woman;
Woman.name = "girl";
Woman.age = 22;
Man.Talk();
Woman.Talk();
}
//output:
I am wyj, 23 years old
I am girl, 22 years old
关键字 public 和 private
在面向对象编程语言中,抽象是一个非常重要的概念, 作为类的设计者,使用 C++关键字 public 和 private 来指定哪些部分可从外部(如 main( ))访问,哪些部分不能。
private私有属性和方法,访问和修改的唯一的途径就是通过类的public公有方法,这个以编写类的程序员认为的合适方式暴露。
1 | class Human |
构造函数
构造函数是一种特殊的函数,它与类同名且不返回任何值,在实例化对象时被调用。
声明和实现
1 | //在类声明中实现 |
构造函数总是在创建对象时被调用,这让构造函数是将类成员变量(int、指针等)初始化为选定值的理想场所。
与函数一样,构造函数也可重载,创建对象时提供不同的参数会调用不同的构造函数,(可在不提供参数的情况下调用的构造函数被称为默认构造函数)
1 | class Human |
另一种初始化成员的方式是使用初始化列表, 冒号后面列出了各个成员变量及其初始值, 可以将上下代码对比着看:
1 | class Human |
析构函数
也是一种特殊的函数,与类同名,但前面有一个腭化符号(~)
构造函数在实例化对象时被调用,而析构函数在对象销毁时自动被调用。
声明和实现
1 | //在类声明中实现(定义) |
每当对象不再在作用域内或通过 delete 被删除进而被销毁时,都将调用析构函数。这使得析构函数成为重置变量以及释放动态分配的内存和其他资源的理想场所
如:某个类中,在构造函数中new, 在析构函数中delete, 使该类不仅对程序员隐藏了内存管理实现,还正确地释放了分配的内存。
(析构函数不能重载)
复制构造函数
浅复制的问题:复制类的对象时,将复制其指针成员,但不复制指针指向的缓冲区,其结果是两个对象指向同一块动态分配的内存。销毁其中一个对象时,delete[]释放这个内存块,导致另一个对象存储的指针拷贝无效。这种复制被称为浅复制,会威胁程序的稳定性
因此使用复制构造函数确保深复制,这是一个重载的构造函数,每当对象被复制时,编译器都将调用复制构造函数。
1 | class MyString |
(类包含原始指针成员(char* 等)时,务必编写复制构造函数和复制赋值运算符。
务必将类成员声明为 std::string 和智能指针类(而不是原始指针),因为它们实现了复制构造函数,可减少工作量。)
移动构造函数 MyString(MyString&& moveSource)
:编译器将自动使用它来“移动”临时资源,从而避免深复制
构造函数和析构函数的其他用途
禁止类对象被复制:声明一个私有的复制构造函数 private: President(const President&);
只能有一个实例的单例类:使用关键字 static
1 | class President |
禁止在栈中实例化的类(栈空间通常有限): 将析构函数声明为私有的
1 | class MonsterDB |
使用构造函数进行类型转换:
1 | class Human |
this 指针 : 在类中,关键字 this 包含当前对象的地址,当您在类成员方法中调用其他成员方法时,编译器将隐式地传递 this 指针—函数调用中不可见的参数Talk("Bla bla"); // same as Talk(this, "Bla Bla")
*this表示当前对象的指针。它是一个特殊的指针,指向类的实例或对象自身
sizeof( ) : 指出类声明中所有数据属性占用的总内存量,单位为字节 (结果受字填充word padding和其他因素的影响)
关键字 struct 来自 C 语言,在 C++编译器看来,它与类极其相似,差别在于程序员未指定时,默认的访问限定符(public 和 private)不同,不同于结构,类的成员默认为私有
1 | //C++ Class |
声明友元: 使用关键字 friend ,从外部访问类的私有数据成员和方法
1 | private: |
共用体:使用关键字 union声明,是一种特殊的类,每次只有一个非静态数据成员处于活动状态。在结构中,常使用共用体来模拟复杂的数据类型
1 | union UnionName |
聚合初始化:即满足如下条件的类或结构为聚合类型,可作为一个整体进行初始化:只包含公有和非静态数据成员,而不包含私有或受保护的数据成员;不包含任何虚成员函数;只涉及公有继承(不涉及私有、受保护和虚拟继承);不包含用户定义的构造函数。
1 | struct Aggregate2 |
定义常量表达式的关键字 constexpr 也可用于类和结果为常量的对象constexpr Human(int humansAge) :age(humansAge) {}
10.实现继承
面向对象编程基于 4 个重要方面:封装、抽象、继承和多态。继承是一种强大的属性重用方式,是通向多态的跳板.
继承和派生
继承: 从一个包含通用属性且实现了通用功能的基类(超类)派生出类似的类,并在派生类(子类)中覆盖基本功能,以实现让每个类都独一无二的行为。
公有继承 public:
1 | class Base |
基类初始化 向基类传递参数: 如果基类包含重载的构造函数,需要在实例化时给它提供实参,就使用初始化列表,并通过派生类的构造函数调用合适的基类构造函数
1 | class Base |
覆盖基类: 派生类实现从基类继承的函数,且返回值和特征标相同的情况
1 | class Base |
构造顺序:基类对象在派生类对象之前被实例化,实例化时,先实例化成员属性,再调用构造函数;析构顺序正好相反。
私有继承 private
私有继承使得只有子类才能使用基类的属性和方法,继承派生类的类不能访问基类的成员, 因此也被称为 has-a 关系, 指定派生类的基类时使用关键字 private:
1 | class Base |
保护继承 protected
继承派生类的类能够访问基类的公有和保护方法,但不能通过派生类的对象来访问基类的公有成员;
使用访问限定符 protected: 对需要继承的基类属性进行保护,让基类的某些属性能在派生类中访问,但不能在继承层次结构外部访问
1 | class Derived: protected Base |
切除(slicing)问题: 复制对象时不要按值传递参数,而应以指向基类的指针或 const 引用的方式传递
多继承:
1 | class Derived: public Base1, publice Base2 |
使用 final 禁止继承: class Derived final: public Base1, publice Base2
要建立 is-a 关系,务必创建公有继承层次结构。
要建立 has-a 关系,务必创建私有或保护继承层次结构。(仅当必要时才使用私有或保护继承)
无论继承关系是什么,派生类都不能访问基类的私有成员。一个例外是类的友元函数和友元类
11.多态
面向对象编程的核心——多态
多态:将派生类对象视为基类对象,并执行派生类的实现
虚函数 virtual
使用虚函数实现多态行为
1 | class Base |
使用关键字 virtual, Swim( )被声明为虚函数,确保编译器调用覆盖版本
对于将被派生类覆盖的基类方法,务必将其声明为虚函数。
作用:对于使用 new 在自由存储区中实例化的派生类对象,如果将其赋给基类指针,并通过该指针调用 delete,将不会调用派生类的析构函数。这可能导致资源未释放、内存泄露等问题,因此可将析构函数声明为虚函数
1 | class Base |
抽象基类和纯虚函数
不能实例化的基类被称为抽象基类,这样的基类只有一个用途,那就是从它派生出其他类(充当接口)。在 C++中,要创建抽象基类,可声明纯虚函数。
1 | class AbstractBase |
抽象基类提供了一种非常好的机制,能够声明所有派生类都必须实现的函数。如果 Trout 类从Fish 类派生而来,但没有实现 Trout::Swim( ),将无法通过编译
虚继承 virtual
使用虚继承解决菱形问题:在继承层次结构中,继承多个从同一个类派生而来的基类时,如果这些基类没有采用虚继承,将导致二义性,因此,如果派生类可能被用作基类,派生时最好使用虚继承:
1 | class Derived1: public virtual Base |
用于创建继承层次结构和声明基类函数时,关键字 virtual 的作用不同:
在函数声明中,virtual 意味着当基类指针指向派生对象时,通过它可调用派生类的相应函数。
从 Base 类派生出 Derived1 和 Derived2 类时,如果使用了关键字 virtual,则意味着再从Derived1 和 Derived2 派生出 Derived3 时,每个 Derived3 实例只包含一个 Base 实例。
表明覆盖意图的限定符 override , 来核实被覆盖的函数在基类中是否被声明为虚的
1 | class Tuna:public Fish |
在派生类中声明要覆盖基类函数的函数时,务必使用关键字 override。
使用 final 来禁止覆盖函数, 被声明为 final 的虚函数,不能在派生类中进行覆盖
1 | class Tuna:public Fish |
虚函数 Clone 模拟虚复制构造函数:
1 |
|
Cmake
CMake 是一个跨平台的开源构建管理系统,用于自动化应用程序的构建、测试和打包过程。它使用类似于Makefile的文本文件来描述构建过程中所需的所有组件和依赖项,并将其转换为适合各种不同编译器和操作系统的本地构建系统的配置文件。总之,CMake就是一个将多个cpp,hpp文件组合构建为一个大工程的语言。
CMake下载 (Linux无需下载)
Cmake 实践 在实践中上手的教程
cmake-examples-Chinese 例程
C-coding/Cmake at main · Arrowes/C-coding
Cmake 实践
t1 创建Hello world
建立main.c与CMakeLists.txt并编译(需要为每一个子目录建立一个CMakeLists.txt)
1 | PROJECT (HELLO) #PROJECT(projectname [CXX] [C] [Java]) |
目标文件:在linux下,是ELF格式(Executable Linkable Format,可执行可链接格式),而在windows下是PE(Portable Executable,可移植可执行)。通常有三种形式:
- 可执行目标文件。即我们通常所认识的,可直接运行的二进制文件。
- 可重定位目标文件。包含了二进制的代码和数据,可以与其他可重定位目标文件合并,并创建一个可执行目标文件。
- 共享目标文件。它是一种在加载或者运行时进行链接的特殊可重定位目标文件。
t2 完善项目并安装
1 | #在CMakeLists中加入 |
t3 lib静态库和动态库构建
1 | SET(LIBHELLO_SRC hello.c) |
静态库.a(Static Library),所有函数和数据都在编译时被静态链接到可执行文件中。文件较大,但不容易受到环境变量和库版本变化的影响,能够提供更好的性能和稳定性。
动态库.so(Dynamic Library)(共享库),在程序运行时才被加载到内存中,而不是在程序编译时被静态链接到可执行文件中,每个动态库只需要一个副本,可以供多个程序使用,因此可以减小可执行文件的大小,减少内存占用,并且如果库文件更新,则只需要替换动态库文件即可,但由于需要在运行时加载库文件,因此可能会稍微降低程序的启动和运行速度。
t4 使用外部共享库和头文件
1 | #在src/CMakeLists.txt中添加头文件.h搜索路径 |
常用变量与环境变量
1 | #常用变量 |
cmake常用指令
1 | make VERBOSE=1 #查看make过程 |
t5,t6 模块
1 | #系统预定义的模块 |
Cmake Opencv Demo
1.安装OpenCV
1 | git clone https://github.com/opencv/opencv.git |
2.写主程序
边缘提取程序
1 |
|
使用OpenCV的canny算子检测边缘
3.写CMake
1 | cmake_minimum_required(VERSION 2.8) |
4.编译运行
1 | mkdir build && cd build |

基于VScode用cmake搭建C++编译调试环境
- 安装VScode插件:C/C++,cmake,cmake tools
- 按F1,选择cmake:Quick Start,创建一个cmake工程
- 点击左侧栏的CMake工具按钮,右键可执行文件,选择Debug,进入调试界面
gcc/g++,MinGW/MSVC与make/CMake/qmake
GNU/Linux:简称Linux,包括Ubuntu,Debian,CentOS,自带gcc;
gcc/g++ :GNU编译器套件(GNU Compiler Collection),在Linux或MacOS上使用,gcc主要用于C语言,g++支持更多的C++特性。
MinGW(Minimalist GNUfor Windows),是Windows下运行的GNU环境,包含gcc和一系列工具,让开发者在Windows下可以写GNU的c/c++代码, 编译的结果是windows的可执行文件exe;
MSVC:微软开发的C/C++编译器,在Windows下编译C/C++程序。它被集成在Visual Studio IDE中。
Makefile包含了描述如何编译和链接程序的规则和指令,指定哪些文件需要先编译,后编译以及重新编译,甚至更复杂的功能操作,通常被用于自动化构建C/C++项目;
Make是一个自动化构建工具,执行Make命令时,它会读取Makefile中的规则,并根据依赖项关系来判断哪些规则需要被执行,来实现编译、链接等操作。
CMake是一个跨平台的自动化构建工具,与Make类似,但是它不直接构建项目,而是生成适合不同构建系统的配置文件,如Makefile或Visual Studio的.sln文件,并调用相应的构建系统来进行项目构建。
qmake是Qt框架提供的自动化构建工具,用于构建Qt项目。