一、环境配置
由于之前安装过QT,所以直接连接网络之后,运行
运行之后检查安装版本
接着用qt的使用步骤
创建工程即可
三、
1、注释
单行注释://
多行注释/*
*/
2、auto
自动推导类型
2.1声明变量
使用auto
声明变量时,变量的类型将根据初始化表达式的类型自动推导。例如:
auto x = 42; // 推导为 int
auto y = 3.14; // 推导为 double
auto name = "John"; // 推导为 const char*
2.2 函数返回类型
从C++14开始,允许在函数定义中使用auto
作为返回类型的占位符,编译器会根据函数体内的返回语句自动推导返回类型。例如:
auto add(int a, int b) {
return a + b; // 返回类型为 int
}
2.3 范围迭代器
在处理容器或数组时,auto
可以简化迭代器的使用。例如,遍历std::vector
中的元素:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
// 或者使用C++11的范围for循环
for (auto num : numbers) {
std::cout << num << " ";
}
return 0;
}
2.3 范围迭代器
在处理容器或数组时,auto
可以简化迭代器的使用。例如,遍历std::vector
中的元素:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
// 或者使用C++11的范围for循环
for (auto num : numbers) {
std::cout << num << " ";
}
return 0;
}
2.4 与引用结合使用
auto
也可以与引用结合使用,自动推导引用的类型。例如:
int x = 5;
auto& ref = x; // 推导为 int&
2.5 模板编程
在模板编程中,auto
可以自动推导模板参数的类型,简化代码。例如:
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
int main() {
auto result = add(10, 2.5); // 结果类型是 double
return 0;
}
注意事项:
- 使用
auto
时,初始化表达式必须提供足够的信息以便推导出变量的类型,否则将导致编译错误。auto
不能用于函数的参数、返回类型和全局变量的声明。- 在需要明确指定类型的情况下,尤其是在函数接口和公共代码中,最好使用显式的类型声明以提高代码的可理解性和可维护性。
auto
不是一个动态类型,变量的类型在编译时就已经确定。
3、引用
int &ref 指的是 ref是一个int指向的地址 的别名
引用必须初始化 当产生了别名之后,别名无法改变,如果出现
只是达到了将j赋值给i的作用。
主要是在函数中使用引用传参,实现被调修改主调的作用。
例子:
实现数值交换
4、内联函数
在C++中,内联函数(Inline functions)是一种特殊的函数,其主要目的是为了解决程序中函数调用的效率问题。当你将一个函数声明为内联函数时,编译器会尝试将该函数的代码直接嵌入到每个调用点,以避免函数调用的开销.。需要在函数声明致歉加上inline关键字
#include <iostream>
using namespace std;
// 声明内联函数
inline int Add(int a, int b) {
return a + b;
}
int main() {
int result = Add(5, 3); // 在这里,编译器会将Add函数的调用替换为具体的函数体代码
cout << "The result is: " << result << endl;
return 0;
}
内联函数的注意事项
-
代码膨胀:过度使用内联函数可能会导致生成的代码体积显著增大,因为每个调用点都会嵌入函数的代码。
-
编译器决策:即使你将函数声明为内联,编译器也可能因为各种原因(如函数体太大、包含循环、递归调用等)决定不将其内联。因此,内联只是一个向编译器的请求,而不是命令。
-
类成员函数:类定义中的成员函数在类定义内部默认是内联的,但如果在类定义外部定义该函数,则需要显式地使用
inline
关键字。 -
虚函数:虚函数不能是内联函数,因为虚函数的调用地址是在运行时确定的,而内联函数的展开是在编译时进行的。
-
内联函数只放头文件里不放在源文件中,因为使用内联函数需要在主调函数之前声明内联函数的内容。
内联函数的使用场景
- 函数体非常小,通常只有几行代码。
- 函数被频繁调用,且调用开销相对于函数执行时间来说较大。
5,带有默认型参数的函数
这样,在调用函数时,如果没有提供这些参数的值,函数将自动使用定义时指定的默认值。这一特性使得函数更加灵活,能够适应更多的使用场景,同时也减少了重载函数的需要。
#include <iostream>
using namespace std;
void print(int a, int b = 2) {
cout << a << ", " << b << endl;
}
int main() {
print(1); // 输出:1, 2
print(1, 3); // 输出:1, 3
return 0;
}
带参数的情况只放源文件,不放头文件。
同时避免头文件包含头文件。
6、重载函数
函数重载是指在同一个作用域内,存在多个同名函数,但这些函数的参数列表不同。参数列表的不同可以包括参数的个数不同、参数的类型不同或参数的顺序不同。需要注意的是,仅仅返回类型的不同不足以构成函数重载。(即同名不同参)。
函数重载的实现条件
- 函数名必须相同:重载函数的名称必须完全相同。
- 参数列表必须不同:重载函数的参数列表(包括参数的个数、类型或顺序)必须有所不同。
- 返回类型可以不同:虽然返回类型不同不足以构成函数重载,但重载函数可以有不同的返回类型。
- const成员函数与非const成员函数可以构成重载:同名的const成员函数(即this指针为const类型)与非const成员函数也被视为重载
#include <iostream>
using namespace std;
// 第一个重载函数,接受一个int参数
void print(int i) {
cout << "Printing int: " << i << endl;
}
// 第二个重载函数,接受一个double参数
void print(double f) {
cout << "Printing float: " << f << endl;
}
// 第三个重载函数,接受一个char参数
void print(char* c) {
cout << "Printing character: " << c << endl;
}
int main() {
// 调用重载函数
print(5); // 调用print(int)
print(500.263); // 调用print(double)
print("Hello"); // 调用print(char*)
return 0;
}
二、面向对象程序设计的基本特点
1、抽象、
对于抽象,我们并不陌生,这是人类认识问题的最基本手段之一。面向对象方法中的抽象,是指对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。抽象的过程,也是对问题进行分析和认识的过程。在面向对象的软件开发中,首先注意的是问题的本质及描述,其次是解决问题的具体过程。一般来讲,对一个问题的抽象应该包括两个方面:数据抽象和行为抽象(或称为功能抽象、代码抽象)。前者描述某类对象的属性或状态,也就是此类对象区别于彼类对象的特征;后者描述的是某类对象的共同行为或功能特征。下面来看两个简单的例子。首先我们在计算机来上实现一个简单的时钟程序。通过对时钟进行分析可以看出,需要3个整型数来存储时间,分别表示时、分和秒,这就是对时钟所具有的数据进行抽象。另外,时钟要具有显示时间、设置时间等简单的功能,这就是对它的行为的抽象。用C++的变量和函数可以将抽象后的时钟属性描述如下:数据抽象:
int hour,int minute,int second
功能抽象:showTime (), setTime()
2、封装
封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机的结合,形成“类”,其中的数据和函数都是类的成员。例如在抽象的基础上,可以将时钟的数据和功能封装起来,构成一个时钟类。
按照C++的语法,时钟类的定义如下:
这里定义了一个名为Clock的类,其中的函数成员和数据成员,描述了抽象的结果。“{"和“}”限定了类的边界。关键字public和private是用来指定成员的不同访问权限的,声明为public的两个函数为类提供了外部接口,外界只能通这个接口来与Clock类发生联系。声明为private的3个整型数据是本类的私有数据,外部无法直接访问。可以看到,通过封装使一部分成员充当类与外部的接口,而将其他成员隐蔽起来,这样就达到了对成员访问权限的合理控制,使不同类之间的相互影响减少到最低限度,进而增强数据的安全性。
3、继承
现实生活中的概念具有特殊与一般的关系。例如,一般意义的“人”都有姓名、性别、年龄等属性和吃饭、行走、工作、学习等行为,但按照职业划分,人又分为学生、教师、工程师、医生等;每一类人又有各自的特殊属性与行为,例如学生具有专业、年级等特殊属性和升级、毕业等特殊行为,这些属性和行为是医生所不具有的。如何把特殊与一般的概念间的关系描述清楚,使得特殊概念之间既能共享一般的属性和行为,又能具有特殊的属性和行为呢?继承,就是解决这个问题的。只有继承,才可以在一般概念基础上,派生出特殊概念,使得一般概念中的属性和行为可以被特殊概念共享,摆脱重复分析、重复开发的困境。C++语言中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。通过类的这种层次结构,可以很好地反映出特殊概念与一般概念的关系。第7章将详细介绍类的继承。
4、多态
面向对象程序设计中的多态是对人类思维方式的一种直接模拟,比如我们在日常生活中说“打球”,这个“打”,就表示了一个抽象的信息,具有多重含义。我们可以说:打篮球、打排球、打羽毛球,都使用“打”来表示参与某种球类运动,而其中的规则和实际动作却相差甚远。实际上这就是对多种运动行为的抽象。在程序中也是这样的,第3章介绍的重载函数就是实现多态性的一种手段。从广义上说,多态性是指一段程序能够处理多种类型对象的能力。在C++语言中,这种多态性可以通过强制多态、重载多态、类型参数化多态、包含多态4种形式来实现。强制多态是通过将一种类型的数据转换成另一种类型的数据来实现的,也就是前面介绍过的数据类型转换(隐式或显式)。重载是指给同一个名字赋予不同的含义,在第3章介绍过函数重载,第8章还将介绍运算符重载。这两种多态属于特殊多态性,只是表面的多态性。包含多态和类形参数化多态属于一般多态性,是真正的多态性。C++中采用虚函数实现包含多态
三、类和对象
1、其本概念(c++类定义)
对象——对象具有状态和行为。例如:一只狗的状态-颜色,名称,品种 或 行为-摇动,叫唤,吃.
对象是类的实例。
类——类可以定义为描述对象行为/状态的模板,蓝图。
方法-从基本上说,一个方法表示一种行为。。一个类可以包含多个方法。可以在方法中写入逻辑以及执行的动作
计时变量-每个对象都有其独特的即时变量对象的装特都是由这些即时变量的值创建的。
定义一个类需要使用关键字 class,然后指定类的名称,并类的主体是包含在一对花括号中,主体包含类的成员变量和成员函数。
定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
以时钟为例:
class Clock{
public:
void setTime(int newH,int newM, int newS);
void showTime();
private:
int hour,minute,second;
};
封装了时钟的数据和行为,分别称为clock类的数据成员和函数成员。
class 类名称
{
public:
外部接口
protected:
保护型成员
private:
私有成员
};
public
部分包含了类的外部接口,这是类与外部世界交互的窗口。任何外部代码都可以访问这些公有成员。protected
部分包含了那些仅对类本身和其派生类(子类)可见的成员。这提供了一种封装的方式,使得基类的一些功能和属性可以在派生类中被保留和扩展,同时对外部代码保持隐藏。private
部分则包含了类的内部实现细节,这些私有成员只能被类自己的成员函数访问。这是实现封装的关键部分,它确保了类的内部状态和行为不会被外部代码直接修改,从而维护了类的完整性和稳定性
练习:设计程序计算圆环面积
#include <iostream>
#include "func.h"
using namespace std;
class Circle
{
public:
void setRadius(double r)
{
radius = r;
}
double area()
{
return radius * radius *3.14;
}
private:
double radius;
};
int main()
{
Circle c1;
c1.setRadius(6);
Circle c2;
c2.setRadius(4);
cout << "面积:"<<c1.area()-c2.area()<<endl;
cout <<"hello world"<<endl;
return 0;
}
想要写的再标准一点的话:
其他问题:
(一)关于字节长度,对于类(class)来说,
sizeof
运算符计算的是该类的一个实例所占的字节大小。这个大小包括了类中的所有非静态数据成员的大小,以及可能的内部填充(padding)或对齐(alignment)所需的额外空间。需要注意:
非静态数据成员的大小:类的每个非静态数据成员都会占用一定的空间,这些空间的总和是类大小的一部分。
对齐(Alignment):为了提高内存访问的效率,编译器可能会在成员之间或类的末尾添加填充字节(padding bytes),以确保每个成员都按照特定的对齐要求存储。
虚函数:如果类中有虚函数,那么类的每个实例都会有一个指向虚函数表的指针(通常是
vptr
)。这个指针也会占用一定的空间。继承:如果一个类继承自另一个类,那么它的大小将至少是其所有非静态数据成员的大小加上基类的大小。
(二)关于 class和struct 都可以用来声明自定义类;
- 默认的访问权限:
struct
的默认访问权限是public
。class
的默认访问权限是private
。- 继承时的默认访问权限:
- 当使用
struct
进行继承时,默认的继承方式是public
继承。- 当使用
class
进行继承时,默认的继承方式是private
继承。
四、构造函数和析构函数
类和对象的关系就相当于基本数据类型与它的变量的关系,也就是一般与特殊的关系。每个对象区别于其他对象的地方主要有两个:外在的区别就是对象的名称,而内在的区别就是对象自身的属性值,即数据成员的值。就像定义基本类型变量时可以同时进行初始化一样,在定义对象的时候,也可以同时对它的数据成员赋初值。在定义对象的时候进行的数据成员设置,称为对象的初始化。在特定对象使用结束时,还经常需要进行一些清理工作。C++程序中的初始化和清理工作,分别由两个特殊的成员函数来完成,它们就是构造函数和析构函数。
1、构造函数
(1)普通构造函数
在C++中,构造函数是一个特殊的成员函数,它在创建对象时自动调用,用于初始化对象。构造函数的名称必须与类名完全相同,并且不能具有返回类型,连void
也不行。构造函数可以重载,意味着一个类可以有多个构造函数,只要它们的参数列表不同即可。
#include <iostream>
class Clock {
public:
Clock(int newH, int newM, int newS); // 构造函数
Clock(); // 构造函数
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
Clock::Clock(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
Clock::Clock() {
hour = 0;
minute = 0;
second = 0;
}
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime() {
std::cout << hour << ":" << minute << ":" << second << std::endl;
}
int main() {
Clock c1(0, 0, 0); // 调用有参数的构造函数
Clock c2; // 调用无参数的构造函数
c1.showTime();
c2.showTime();
c2.setTime(12, 30, 45);
c2.showTime();
return 0;
}
(2)默认构造函数
在C++中,默认构造函数是一个特殊的构造函数,它在未指定其他构造函数时由编译器自动生成。这个构造函数的目的是进行默认的初始化操作。需要注意的是,如果我们已经定义了其他构造函数,编译器就不会再自动生成默认构造函数了。
也可以说,在开空间的是同时进行初始化。
默认构造函数有以下两个主要特征:
-
无参数:默认构造函数不接受任何参数。
-
默认行为:若类中包含内置类型成员,如
int
或pointer
,这些成员不会被初始化(除非C++11或更高版本中的规则另有规定,此时它们将被零初始化)。若类中包含类类型成员,且该类类型具有默认构造函数,则这些成员将被其默认构造函数初始化。
class MyClass {
public:
int value;
// 默认构造函数未显式定义,编译器将自动生成
};
int main() {
MyClass obj; // 创建MyClass类型的对象,将调用默认构造函数
// 此时,obj.value的值是未定义的,因为它没有被初始化
return 0;
}
变成
class MyClass {
public:
int value;
MyClass() : value(0) { // 显式定义默认构造函数,并初始化value为0
}
};
int main() {
MyClass obj; // 创建MyClass类型的对象,将调用我们定义的默认构造函数
// 此时,obj.value的值被初始化为0
return 0;
}
在时钟函数中修改成默认初始化的情况如下:
Clock::Clock(int newH, int newM, int newS) : hour(newH), minute(newM), second(newS) {
}
#include <iostream>
class Clock {
public:
// 构造函数,添加默认形参值
Clock(int newH = 0, int newM = 0, int newS = 0);
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
Clock::Clock(int newH, int newM, int newS) : hour(newH), minute(newM), second(newS) {
}
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime() {
std::cout << hour << ":" << minute << ":" << second << std::endl;
}
int main() {
// 使用默认形参值创建对象
Clock c1; // 调用构造函数,使用默认值0, 0, 0
Clock c2(12, 30, 45); // 调用构造函数,使用指定的值
c1.showTime(); // 显示0:0:0
c2.showTime(); // 显示12:30:45
return 0;
}
练习:
编写一个初始化列表的方式来初始化实部和虚部:
(3)复制构造函数
①使用另一个同类型的对象直接初始化新对象
Clock c1(10, 20, 30);
Clock c2 = c1; // 调用复制构造函数
②函数按值传递参数时:
当函数参数是按值传递时(不是引用或指针),调用函数时会将实参复制一份给形参,此时会调用复制构造函数。
void func(Clock c) {
// ...
}
Clock c1(10, 20, 30);
func(c1); // 调用复制构造函数
③函数返回局部对象时:
如果函数返回一个局部对象,那么在返回过程中会调用复制构造函数来创建一个临时对象作为返回值
Clock func() {
Clock c(10, 20, 30);
return c; // 调用复制构造函数(但现代C++编译器可能使用返回值优化RVO来避免这次复制)
}
④在数组初始化时:
如果使用对象初始化数组,那么每个数组元素都会调用复制构造函数。
Clock c1(10, 20, 30);
Clock arr[3] = {c1, c1, c1}; // 每个元素都调用复制构造函数
⑤在容器中添加元素时:
当使用std::vector
、std::list
等容器,并且向其中添加对象时,也会调用复制构造函数
std::vector<Clock> vec;
Clock c1(10, 20, 30);
vec.push_back(c1); // 调用复制构造函数
有些构造函数的结果会省略构造函数的过程,达到优化的效果。
使用图中的命令就可以达到不省略的效果。
在qt上加上也可以
2、析构函数
析构函数是一个特殊的成员函数,它在对象生命周期结束时自动被调用,用于执行清理工作,例如释放对象所占用的资源。析构函数的名字由波浪号~
后跟类名组成,它没有返回类型,也不接受任何参数。析构函数是自动被调用的,不需要手动调用。当对象的生命周期结束时,无论是局部对象离开作用域,还是动态分配的对象被delete
,析构函数都会被自动调用
对于上文中的
#include <iostream>
class Clock {
public:
// 构造函数
Clock(int newH = 0, int newM = 0, int newS = 0);
// 析构函数
~Clock();
// 设置时间的成员函数
void setTime(int newH, int newM, int newS);
// 显示时间的成员函数
void showTime() const;
private:
int hour, minute, second;
};
// 构造函数实现
Clock::Clock(int newH, int newM, int newS) : hour(newH), minute(newM), second(newS) {
// 构造函数体可以为空,因为我们已经使用初始化列表初始化了成员变量
}
// 析构函数实现
Clock::~Clock() {
// 在这里执行清理工作,对于简单的Clock类来说,并不需要特别的清理工作
// 但如果Clock类包含了动态分配的内存或其他资源,就需要在这里释放它们
std::cout << "Clock destructor called." << std::endl;
}
// 设置时间的成员函数实现
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
// 显示时间的成员函数实现
void Clock::showTime() const {
std::cout << hour << ":" << minute << ":" << second << std::endl;
}
int main() {
Clock c1(10, 20, 30); // 使用构造函数创建一个Clock对象c1
c1.showTime(); // 显示c1的时间
// 当c1离开作用域时,它的析构函数会被自动调用
return 0;
}