一、 QT Creator 使用基本介绍
-
创建并运行第一个 QT 项目
-
创建并运行第一个 C++ 项目
-
QT Creator 的界面介绍
-
QT Creator 常用的快捷键介绍
功能 快捷键 中文说明 撤销 Ctrl + Z 撤销最近的操作 重做 Ctrl + Y 重做最近的撤销操作 复制 Ctrl + C 复制选中内容 粘贴 Ctrl + V 粘贴内容 复制行向下 Ctrl + Alt + Down 将当前行复制到下一行 复制行向上 Ctrl + Alt + Up 将当前行复制到上一行 运行 Ctrl + R 运行当前项目 返回编辑模式 Esc 返回到编辑状态 切换当前文件 Ctrl + Tab 在打开的文件间切换 切换声明和定义 F2 在代码的声明与定义间切换 切换头文件和源文件 F4 在头文件和源文件间切换 开始调试 F5 启动调试 停止调试 Shift + F5 停止当前的调试 构建当前项目 Ctrl + B 构建当前打开的项目 构建所有项目 Ctrl + Shift + B 构建所有项目 新建文件或项目 Ctrl + N 创建新文件或项目 打开文件或项目 Ctrl + O 打开现有文件或项目 保存当前文件 Ctrl + S 保存当前编辑的文件 保存所有文件 Ctrl + Shift + S 保存所有打开的文件 关闭当前文件 Ctrl + W 关闭当前文件 关闭所有文件 Ctrl + Shift + W 关闭所有打开的文件 退出 QT Creator Ctrl + Q 退出 QT Creator 位置后退 Alt + Left 光标位置回退
二、 C++ 基础
C 和 C++之间的关系是紧密且复杂的。C++ 最初是作为 C 语言的一个扩展开发的,目的是在不放弃 C 的强大功能和效率的同时,增加对象导向编程、泛型编程和其他一些特征。下面是 C 和 C++ 之间主要的关系和区别:
- **兼容性:**C++ 在很大程度上是与 C 兼容的。这意味着许多 C 程序可以在 C++编译器中编译并运行,尽管可能需要一些小的修改。
- **面向对象编程(OOP):**C++ 引入了面向对象编程。它允许使用类和对象,而 C 是一个过程性语言,不支持这些概念,或者说支持的不好,麻烦。
- **模板:**C++ 支持目标,这是一种允许程序员编写与数据类型无关的代码的功能,C 没有这个功能。
- **标准库:**C++ 有一个更丰富的标准库,包括
STL
(标准模板库),这为数据结构和算法提供了广泛的支持,而 C 的标准库相对较小。 - **类型检查:**C++ 比 C 提供更严格的类型检查,这意味着某些在 C 中可行但可能导致错误的代码在 C++ 中可能无法编译。
- **异常处理:**C++ 支持异常处理,这是一种处理程序运行时错误的机制,C 没有内置的异常处理机制。
- **命名空间:**C++ 引入了命名空间,这有助于防止名称冲突,C 没有这个概念。
2.1 命名空间
2.1.1 命名空间作用
创建自己的命名空间是 C++ 中组织代码的一种好方法,特别是在开发大项目或库时,命名空间可以帮助你避免名称冲突,并且清晰地组织代码。
std
是 C++标准库的命名空间。它是一个定义在 C++标准库中的所有类、函数和变量的命名空间。
新建一个 QTCreator 的 C++工程,默认生成代码
#include <iostream>
using namespace std;
int main()
{
cout << "Hello Word! C++" << endl;
return 0;
}
在 C++ 中,如果想使用标准库中的任何类、函数或对象,通常有两种选择:
-
使用
std::
前缀: 这是最常见的方式,它明确指定了正在使用的是位与std
命名空间中的元素。std::cout << "Hello Word!" << std::endl;
-
**使用
using namespace std;
:**这允许在不显示指定std::
的情况下使用std
命名空间中的所有元素。using namespace std; cout << "Hello Word!" << endl;
std
包含的内容
std
命名空间包含了许多类、函数和对象,例如:
- 输入输出库(如
std::cout
,std::cin
,std::endl
) - 容器类(如
std::vector
,std::map
,std::set
) - 字符串类(如
std::string
) - 异常类(如
std::exception
和相关子类) - 算法(如
std::sort
,std::find
) - 实用工具(如
std::pair
,std::tuple
) - 其他许多功能
使用建议
- 对于小型代码或示例底阿妈,使用
using namespace std;
通常是安全的。 - 对于大型项目或库,建议显示地使用
std::
前缀,以避免潜在的名称冲突,并提高代码的可读性和可维护性。
std
命名空间是 C++ 编程的基础部分,理解和正确使用它对于编写健壮和高效的 C++ 代码至关重要。
2.1.2 自定义命名空间
定义命名空间
假设创建一个命名空间来包含与圆形相关的功能,可以命名这个命名空间为Cir
#ifndef CIR_H
#define CIR_H
namespace Cir{
const double PI = 3.141592653;
double areaOfCircle(double radius){
return PI*radius*radius;
}
double lenthOfCircle(double radius){
return 2*PI*radius;
}
}
#endif // CIR_H
在这个头文件中,定义了一个名为Cir
的命名空间,其中包含了计算圆的面积和周长的函数,以及圆周率常量PI
。
使用命名空间
在另一个文件中,可以使用这个命名空间中定义的函数和变量:
#include "Cir.h"
#include <stdio.h>
int main()
{
double radius = 5;
printf("半径为%f的圆,周长为%f,面积为%f\n",
radius, Cir::lenthOfCircle(radius), Cir::areaOfCircle(radius));
return 0;
}
在main.cpp
中,首先包含了定义Cir
命名空间的头文件,然后可以使用Cir::
前缀来访问该命名空间中的函数和常量。
通过使用自定义命名空间,可以有效地组织代码,并减少不同库之间的名称冲突,这在大型项目和团队协作中尤其重要。
#include "Cir.h"
#include <stdio.h>
using namespace Cir;
int main()
{
double radius = 5;
printf("半径为%f的圆,周长为%f,面积为%f\n",
radius, lenthOfCircle(radius), areaOfCircle(radius));
return 0;
}
2.2 从 C 语言快速入门
2.2.1 输入输出
C++ 中的输入输出(I/O)主要是通过标准库中的输入输出流来实现的,最常见的是iostream
库,它提供了用于输入输出的基本流,包括cin
、cout
、cerr
和clog
。
标准输出流(cout)
cout
代表标准输出流,通常用于屏幕输出数据。- 使用操作符
<<
(插入操作符)向cout
发送数据。 - 例如,
std::cout << "Hello Word! << std::endl;"
,会在屏幕上打印 “Hello Word!” 并换行。
标准输入流(cin)
cin
代表标准输入流,用于从键盘接收数据。- 使用操作符
>>
(提取操作符)从cin
提取数据。 - 例如,
int x; std::cin >> x;
会从用户那里读取一个整数并存储在变量x
中。
标准错误流(cerr
)和标准日志流(clog
)
cerr
用于输出错误消息,与cout
不同,cerr
不是缓冲的,这意味着它会立即输出。clog
类似于cerr
,但它是缓冲的,它通常用于记录错误和日志信息。
实例代码
下面是一个展示如何使用这些基本流的简单示例:
#include <iostream>
int main()
{
// 使用 cout 输出
std::cout << "Enter a number: ";
// 使用 cin 输入
int num;
std::cin >> num;
// 输出结果
std::cout << "You entered: " << num << std::endl;
std::clog << "Logging: user entered a number." << std::endl;
return 0;
}
2.2.2 基本变量
C++ 基本数据类型整理成表格。以下是一个表格,展示了不同的基本数据类型及其一般用途和大小范围:
和 C 语言类似。
数据类型 | 描述 | 大小(通常情况下) | 用途 |
---|---|---|---|
int | 整型 | 至少16位 | 存储整数 |
short int | 短整型 | 至少16位 | 存储较小的整数 |
long int | 长整型 | 至少32位 | 存储较大的整数 |
long long int | 更长的整型 | 至少64位 | 存储非常大的整数 |
unsigned int | 无符号整型 | 同int | 存储非负整数 |
float | 单精度浮点类型 | 32位 | 存储小数,精度约为 6-7 位小数 |
double | 双精度浮点类型 | 64位 | 存储小数,精度约为 15-16 位小数 |
long double | 扩展精度浮点类型 | 80位或更多 | 存储小数,提供比double 更高的精度 |
char | 字符型 | 8位 | 存储单个字符或小整数 |
unsigned char | 无符号字符型 | 8位 | 存储较大的字符或作为字节使用 |
signed char | 有符号字符型 | 8位 | 明确作为带符号的字符或小整数使用 |
bool | 布尔型 | 通常为8位 | 存储真值true 或假值false C 语言 C99 以上支持 |
wchar_t | 宽字符类型 | 通常为16位或32位 | 存储中文或者unicode |
宽字符的用法
#include <iostream>
#include <locale>
#include <wchar.h>
int main()
{
// 设置本地化以支持宽字符
std::setlocale(LC_ALL, "");
// 使用 wchar_t 类型定义一个宽字符串
wchar_t wstr[] = L"你好,世界!";
// 在 C++ 中打印宽字符串
std::cout << wstr << std::endl;
return 0;
}
在 C++ 中,<climits>
(或在 C 中是<limits.h>
)是一个标准头文件,提供了关于整型限制的信息,这个头文件中定义了各种整型数据类型的属性,如最大值、最小值等,使用这些信息可以帮助你了解在特定编译器和平台上各种数据类型的大小和范围。
如何使用<climits>
要使用<climits>
中定义的常量,首先需要包含这个头文件:
#include <climits>
然后,可以使用它提供的各种常量,例如:
INT_MAX
:int
类型的最大值。INT_MIN
:int
类型的最小值。UINT_MAX
:unsigned int
类型的最大值。LONG_MAX
:long int
类型的最大值。LONG_MIN
:long int
类型的最小值。LLONG_MAX
:long long int
类型的最大值。LLONG_MIN
:long long int
类型的最小值。
示例代码
下面是一个简单的示例,展示了如何使用<climits>
中的值:
#include <iostream>
#include <climits>
int main()
{
std::cout << "The range of int is from " << INT_MIN << " to " << INT_MAX << std::endl;
std::cout << "The maximum value of unsigned int is " << UINT_MAX << std::endl;
std::cout << "The range of long long is from " << LLONG_MIN << " to " << LLONG_MAX << std::endl;
return 0;
}
这个程序会输出int
、unsigned int
和long long int
类型的最大值和最小值。
注意事项
<climits>
提供的是编译时确定的常量,这意味着这些值在编译时就已经固定,根据编译器和平台的不同而可能有所不同。- 使用这些限制值可以帮助你编写更可移植和安全的代码,特别是在处理可能超出数据类型范围的操作时。
2.2.3 流程控制
在 C++ 中,流程控制语句用于根据不同条件控制程序的执行流程,它们是编程中的基本构建块,允许程序根据条件执行不同的代码段,重复执行某些操作,或者根据特定条件跳过某些代码段,下面是 C++ 中最常见的流程控制语句:
条件语句
if 语句
:基于条件的基本控制结构。如果条件为真,则执行代码块。
if(condition) {
// 条件为真时执行的代码
}
else 语句
:与if
语句配合使用。当if
的条件为假时执行。
if(condition) {
// 条件为真时执行的代码
} else {
// 条件为假时执行的代码
}
else if 语句
:多用于测试多个条件。
if(condition1) {
// 第一个条件为真时执行的代码
} else if(condition2) {
// 第二个条件为真时执行的代码
} else {
// 所有条件为假时执行的代码
}
switch 语句
:基于变量的值选择执行不同代码的方法。
switch(expression) {
case value1:
// expression 等于 value1 时执行的代码
break;
case value2:
// expression 等于 value2 时执行的代码
break;
default:
// 没有匹配的 case 时执行的代码
}
循环语句
for 循环
:当知道循环应该执行的次数时使用。
for(initialization; condition; increment) {
// 循环体
}
while 循环
:当条件为真时,重复执行代码块。
while(condition) {
// 循环体
}
do-while 循环
:至少执行一次循环体,然后再检查条件。
do {
// 循环体
} while(condition);
跳转语句
break 语句
:用于立即跳出最近的switch
或循环(for
、while
、do-while
)。continue 语句
:跳过循环的当前迭代,并继续下一次迭代。goto 语句
:直接跳转到程序中的另一个点,使用goto
通常不推荐,因为它可以使代码难以阅读和维护。
流程控制语句是编程中非常重要的部分,允许开发者编写可以根据不同情况改变行为的灵活且强大的程序,在使用这些语句时,应该确保逻辑清晰,以便代码易于理解和维护。
2.2.4 函数
在 C++ 中,函数是一段执行特定任务的代码块,它可以带有参数,并且可能返回一个值,函数的使用使得代码更加模块化和可重用,有助于降低代码的复杂性,并提高可维护性。
函数的基本结构
C++ 函数的基本结构包括返回类型、函数名、参数列表和函数体:
返回类型 函数名(参数列表) {
// 函数体
// 返回语句(如果有返回值的话)
}
示例
以下是一个 C++ 函数的简单示例:
#include <iostream>
using namespace std;
// 函数声明
int add(int x, int y);
int main() {
int result = add(5, 3);
cout<< "Result: " << result << endl;
return 0;
}
// 函数定义
int add(int x, int y) {
return x + y;
}
在这个示例中,add
函数接收两个整数参数,并返回它们的和。
函数的组成部分
- 返回类型:指定函数返回的数据类型,如果函数不返回任何值,则使用
void
。 - 函数名:函数的标识符,用于调用函数。
- 参数列表:括号内的变量列表,用于从函数的调用者那里接收值,如果函数不接收任何参数,则此列表为空。
- 函数体:大括号
{}
内的一系列语句,定义了函数的执行操作。
2.2.5 内联函数
内联函数(Inline Function)是 C++ 中一种特殊的函数,其定义直接在每个调用点展开,这意味着编译器会尝试将函数调用替换为函数本身的代码,这样可以减少函数调用的开销,尤其是在小型函数中。
特点
- 减少函数调用开销:内联函数通常用于优化小型、频繁调用的函数,因为它避免了函数调用的常规开销(如参数传递、栈操作等)。
- 编译器决策:即使函数被声明为内联,编译器也可能决定不进行内联,特别是对于复杂或递归函数。
- 适用于小型函数:通常只有简单的、执行时间短的函数适合做内联。
- 定义在每个使用点:内联函数的定义(而非仅仅是声明)必须对每个使用它的文件都可见,通常意味着将内联函数定义在头文件中。
使用方法
通过在函数声明前添加关键字inline
来指示编译器该函数适合内联:
inline int max(int x, int y) {
return x > y ? x : y;
}
示例
#include <iostream>
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(5, 3); // 编译器可能会将此替换为:int result = 5 + 3;
std::cout<< "Result: " << result << std::endl;
return 0;
}
在这个示例中,函数add
被定义为内联函数,当它被调用时,编译器可能会将函数调用替换为函数体内的代码。
注意事项
- 过度使用的风险:不应滥用内联函数,因为这可能会增加最终程序的大小(代码膨胀),对于大型函数或递归函数,内联可能导致性能下降。
- 编译器的决定:最终是否将函数内联是由编译器决定的,即使函数被标记为
inline
。 - 适用场景:最适合内联的是小型函数和在性能要求高的代码中频繁调用的函数。
内联函数式一种用于优化程序性能的工具,但需要合理使用,以确保代码的可维护性和性能的平衡。
2.2.6 Lambda 表达式
Lambda 表达式是 C++11 引入的一种匿名函数的方式,它允许你在需要函数的地方内联地定义函数,而无需单独命名函数
Lambda 表达式的基本语法如下:
[capture clause](parameters) -> return_type {
// 函数体
// 可以使用捕获列表中的变量
return expression; // 可选的返回语句
}
Lambda表达式由以下部分组成:
- 捕获列表(capture clause):用于捕获外部变量,在 Lambda 表达式中可以访问这些变量,捕获列表可以为空,也可以包含列表
[var1, var2, ...]
。 - 参数列表(parameters):与普通函数的参数列表类似,可以为空或包含参数列表
(param1, param2, ...)
。 - 返回类型(Return_type):Lambda 表达式可以自动推断返回类型,也可以显示指定返回类型
-> return_typ
。如果函数体只有一条返回语句,可以省略返回类型。 - 函数体(Body):Lambda 表达式的函数体,包含需要执行的代码。
Lambda 表达式最简单的案例是在需要一个小型函数或临时函数时直接使用它,以下是一个非常简单的例子,其中使用 Lambda 表达式来定义一个加法操作,并立即使用它来计算两个数的和。
示例:使用 Lambda 表达式进行加法
#include <iostream>
int main() {
// 定义一个简单的 Lambda 表达式进行加法
auto add = [](int a, int b) {
return a + b;
};
// 使用 Lambda 表达式计算两个数的和
int sum = add(10, 20);
std::cout<< "Sum is: " << sum << std::endl;
return 0;
}
2.2.7 数组
在 C++ 中,数组是一种存储固定大小的相同类型元素的序列,数组的所有元素存储在连续的内存位置上,这种数据结构非常适合于存储具有固定数量和相同数据类型的元素集合。
声明数组
声明数组的基本语法如下:
数据类型 数组名[数组大小];
例如,声明一个类型为int
的数组,包含10个元素:
int myArray[10];
初始化数组
在声明数组时,可以同时初始化数组:
int myArray[5] = {10, 20, 30, 40, 50};
在初始化数组时没有指定所有元素的值,未初始化的元素将自动设置为该数据类型的默认值(对于基本数据类型通常是0):
int myArray[5] = {10, 20}; // 其余元素将被初始化为 0
访问数组元素
可以通过指定索引来访问数组中的元素,数组索引是从 0 开始的,所以数组的第一个元素是数组名[0]
,第二个元素是数组名[1]
,依此类推:
int value = myArray[2]; // 访问第三个元素
示例
以下是使用数组的简单示例:
#include <iostream>
using namespace std;
int main()
{
int myArray[5] = {10, 20, 30, 40, 50};
int i = 0;
// 输出所有数组元素的值
for(i = 0; i < 5; i++)
{
cout << "Element at index " << i << ": " << myArray[i] << endl;
}
return 0;
}
注意事项
- 数组的大小必须在编译时已知,且不能更改。
- 数组索引越界是常见的错误,可能会导致未定义的行为。
- 对于更复杂的用例,可能需要使用 C++ 的标准模板库(STL)中的容器,如
std::vector
,它提供了可以动态改变大小的数组。 - 数组的元素存储在连续的内存位置上,这使得访问数组元素非常快。
2.2.8 指针
C++ 完全兼容 C 语言指针,多出一个this
指针。
#include <iostream>
using namespace std;
void swap(int *pa, int *pb)
{
int tmp;
tmp = *pa;
*pa = *pb;
*pb = tmp;
}
int main()
{
int a = 10;
int b = 20;
cout << a << endl;
cout << b << endl;
cout << "after chage: " << endl;
swap(&a, &b);
cout << a << endl;
cout << b << endl;
return 0;
}
2.3 类
2.3.1 类的初探
C++ 中类(class)是一种编程结构,用于创建对象,这些对象可以拥有属性(即数据成员)和行为(即成员函数或方法)。类的概念是面向对象编程的核心之一,其主要目的是将数据和与数据相关的操作封装在一起。例如,如果你有一辆”汽车“类,它可能包含颜色、品牌、型号等属性(数据成员),以及启动、停止、加速等行为(成员函数),每当你基于这个类创建一个对象时,你就有了一个具体的汽车,具有这些属性和行为。
C++ 类的基本结构通常包含:
- 数据成员(Attributes):定义类的属性,这些是类内部的变量,用于存储对象的状态。
- 成员函数(Methods):定义类的行为,这些是可以操作对象的数据成员的函数。
- 构造函数和析构函数:特殊的成员函数,构造函数在创建对象时自动调用,用于初始化对象,析构函数在对象销毁时调用,用于执行清理操作。
- 访问修饰符:如
public
,private
,protected
,用于控制对类成员的访问权限。例如,public
成员可以在类的外部访问,而private
成员只能在类内部访问。 - 继承:允许一个类继承另一个类的特性,这是代码重用和多态性的关键。
通过这些特性,C++ 类提供了一种强大的方式来组织和处理数据,使得代码更加模块化、易于理解和维护。
2.3.2 结构体引入类
如果用 C 语言实现上面描述的汽车类,实现代码如下:
#include <stdio.h>
struct Car{ //汽车“类”
char *color; //颜色
char *brand; //品牌
char *type; //车型
int year; //年限
void(*printCarInfo)(); //函数指针,指向车介绍函数
void(*carRun)(); //函数指针,指向车运行的函数
void(*carStop)(); //函数指针,指向车停止的函数
};
void printCarInfo()
{
printf("this is a Car\n");
}
void CarRun()
{
printf("car is Running\n");
}
void carStop()
{
printf("Car is stop\n");
}
int main()
{
//定义一个汽车变量,让车更加“具体化”
//指定了车的品牌、型号、年限、颜色,车的各项功能
struct Car car;
car.color = "white";
car.brand = "BMW";
car.type = "the 3";
car.year = 2023;
car.printCarInfo = printCarInfo;
car.carRun = carRun;
car.carStop = carStop;
car.printCarInfo();
car.carRun();
car.carStop();
return 0;
}
2.4 权限初识别
2.4.1 基本介绍
C++ 中访问权限主要分为三种:public
,private
,protected
。这些权限决定了类成员(包括数据成员和成员函数)的可访问性,以下是一个总结表格,说明了在不同情况下这些权限如何应用:
访问权限 | 类内部 | 同一个类的对象 | 派生类 | 类外部 |
---|---|---|---|---|
public | 可访问 | 可访问 | 可访问 | 可访问 |
private | 可访问 | 不可访问 | 不可访问 | 不可访问 |
protected | 可访问 | 不可访问 | 可访问 | 不可访问 |
使用权限(如:public
、private
和protected
)在 C++ 中是一种关键的封装手段,它们旨在控制对类成员的访问。下面是一个表格,总结了使用权限的主要好处和潜在缺点:
好处 / 缺点 | 描述 |
---|---|
好处 | |
封装性 | 通过隐藏类的内部实现(私有和受保护成员),提高了代码的安全性和健壮性。 |
接口与实现的分离 | 公开接口(公开成员)与私有实现分离,有助于用户仅关注于如何使用类而不是如何实现 |
易于维护 | 修改类的内部实现不会影响使用该类的代码,从而降低了维护成本。 |
控制读写访问 | 通过设置访问权限,可以精确控制类成员的读写访问。 |
继承的灵活性 | protected 成员在派生类中是可访问的,使得继承更加灵活。 |
缺点 | |
增加复杂性 | 过度使用或不当使用权限可能导致代码结构复杂,难以理解。 |
测试难度 | 私有成员的测试比公共成员更困难,因为它们不能从类的外部访问。 |
灵活性降低 | 过于严格的封装可能限制了某些有效的用法,降低了灵活性。 |
可能导致紧耦合 | 过多依赖friend 类或函数可能导致类之间的耦合过紧。 |