C++面向对象01

一、C++、QT与嵌入式的那些事o,0’

C++:与QT配合搭建图形化开发,阅读linux内核源码打基础

QT:在嵌入式领域中进行图形化开发

市场上的嵌入式设备:

1、单片机:RTOS(FreeRTOS / RTThread)

      跑界面:mini GUI (C/C++)        LVGL(C语言)

2、高端ARM开发板:安卓

      跑界面:专门做安卓应用的开发

3、高端ARM开发板:Linux

      跑界面:QT

国家大力推国产化:

        芯片:龙芯、飞腾、兆易创新

        OS  :鸿蒙、澎湃、优麒麟、红旗 

在国产芯片上移植国产的操作系统就是嵌入式工程师的工作。

国产操作系统以Linux为基础,因此图形化开发也需要我们。

-----------------------------------------------------------------------------------------

要给一个指定的地址存储整数,需要先将地址强转为地址类型

如:

*(int *)(0x12345678) = 100;

*(volatile unsigned int *)(0x12345678) = 100;(常用)

volatile:防止编译器优化(编译器优化指的是,当编译器发现你初始化了一个值,但在接下来的语句中并没有进行操作,则编译器认为你这种行为是没有实际意义的,所以给你优化掉了,就是不执行了)

不可:

0x12345678 = 100 (编译器理解为要对整型数进行赋值,报错)

*(0x12345678) = 100 (编译器理解为要对一个整型数进行取值,报错)

-----------------------------------------------------------------------------------------

1、职位匹配

        (1)、C++开发工程师

        (2)、嵌入式应用开发(C/C++)

        (3)、嵌入式QT开发工程师

2、主要学习内容

        (1)、C到C++过渡

        (2)、类和对象(C++如何创建类、创建对象、如何进行封装?)

        (3)、继承和多态

        (4)、模板(泛型编程)

        (5)、文件IO

        (6)、异常处理

        (7)、多线程

        (8)、STL模板库(数据结构和算法)

3、学习环境

        开发环境                Linux平台

        编辑器                   Vim、VsCode

        编译器                   g++(和gcc一样是GUN的编译器)

        文件后缀                xxx.cpp(C Plus Plus)

二、从C到C++

 1、头文件

        【c】        #include <stdio.h>

        【c++】    #include <iostream> / #include <stdlib>

注意:C++在包换头文件时不需要写“.h”,如果想要包含“stdlib.h”,只需要写成#include <stdlib>

           主要因为C++中有一个概念“命名空间”(namespace)

            早期标准库将所有的功能在全局域中实现,声明在.h文件使用的时候需要包含对应的头文件,后来C++标准引入了命名空间,就把C++的标准库的定义实现都放在了命名空间中。

-----------------------------------------------------------------------------------------

包含头文件时,尖括号<>与双引号""的区别

主要是搜索路径不同

双引号#include“”格式:我们常常引用非标准库的头文件,即我们自己写的头文件,编译器从用户的工作目录开始搜索

               1)在当前源文件所在的工作目录中进行查找

               2)在编译器设置的头文件查找路径,编译器有默认的头文件查找路径(可以在编译时,使用-l显式指定搜索路径)

               3)系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径

尖括号#include<>格式: 我们常常引用标准库头文件,编译器从标准库目录开始搜索

               1)在编译器设置的头文件查找路径,编译器有默认的头文件查找路径(可以在编译时,使用-l显式指定搜索路径)

               2)系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径

-----------------------------------------------------------------------------------------

2、命名空间(namespace)

        将变量名(一般是全局的)(函数名、类型名)限制到一个空间(命名空间)

-----------------------------------------------------------------------------------------

变量的三个关注点(作用域,存储空间,生命周期)

全局变量说明定义在函数外的变量
作用域整个进程
生存周期整个进程
存储位置

未初始化的在bss段,值一定是0

已初始化的在data段

局部变量说明定义在函数内的变量
作用域函数内
生存周期到函数结束
存储位置

栈区

未初始化一定是随机值

局部静态变量说明由static修饰的局部变量
作用域函数内
生存周期整个进程
存储位置

未初始化的在bss段,值一定是0

已初始化的在data段

 -----------------------------------------------------------------------------------------

        1)作用

                缩短变量名的作用域(只在命名空间中有效,用来防止多文件多模块编程时出现命名冲突的问题)

        2)语法

                定义命名空间的方式

namespace myTestname
{
    int a; // 整型变量
    FILE *fp; // 文件流指针
    void test(void); // 函数的声明
} // 注意:没有分号
        3)打开命名空间

                [1]使用域作用限定符号

                        命名空间的名字::变量名

                        例如:myTestname::a、std::cout

                [2]展开命名空间(把限制打开)

                        using namespace 命名空间的名字

                        例如:using namespace std;、usingnamespace myTestname;

                        注意:在写项目时不适合用第二种方式,命名空间中的所有成员都在全局域中,这样会有冲突

        4)输入输出

                C语言是面向过程的语言,输入输出是函数提供的

                C++是面向对象的语言,输入输出是对象

                 [1]标准输入istream----预定义的对象cin

cin >> n; // 读取一个数据,存储到n中

在C语言中,我们使用scanf给一个变量录入数值。 

                 [2]标准输出ostream----预定义的对象cout

cout << "hello" << endl; // 输出一个字符串hello

endl(end of line)是C++标准库中的操控器,代表换行

在输入和输出时用到了“<<”和“>>”,其实就是位运算符,只不过运算符重载了。

cin和cout是由iostream头文件提供的对象,所以想要使用的话,需要包含这个头文件。

3、动态存储空间的开辟和释放

        谁打开谁关闭                文件IO

        谁申请谁释放                堆区的空间(malloc realloc calloc / free)

        谁创建谁销毁                链式存储结构

        谁加锁谁解锁                线程

C语言C++
内存开辟(堆区)malloc、realloc、callocnew
内存释放(堆区)free

delete

        在C++中对内存的开辟与释放需要用到两个关键字(new、delete)

        new、delete不是C++某个类的对象,是运算符

        new是内存分配运算符

        delete是取消内存分配运算符

#include <iostream>

using namespace std;

int main(void)
{
    int *p = NULL;

    p = new int;//动态存储空间的开辟(等价于p = malloc(sizeof(int));)
    if(p == NULL)//判断开辟动态存储空间是否失败
    {
        cerr << "Dynamic memory is Failed!" << endl;
        return -1;
    }

    *p = 9527;

    cout << "[1]p = " << p << " *p = " << *p << endl;

    cin >> *p;

    cout << "[2]p = " << p << " *p = " << *p << endl;

    delete p;//释放动态存储空间(等价于free(p))

    p = NULL;//为了防止野指针

    return 0;
}
#include <iostream>

using namespace std;

int main(void)
{
    char *s = NULL;

    s = new char[32];//动态存储空间的开辟(等价于s = malloc(sizeof(char) * 32))
    if(s == NULL)//判断动态存储空间开辟是否失败
    {
        cerr << "Failed!" << endl;
        return -1;
    }

    cin >> s;//通过终端录入字符串给s

    cout << "s = " << s << endl;//打印输出字符串

    delete[] s;//释放动态开辟的存储空间(等价于free(s))

    s = NULL;//

    return 0;
}

4、函数 (参数从右向左依次入栈)

        1)函数参数的默认值

        在C++中,当我们定义了一个函数,可以为参数列表中的参数指定默认值,当调用该函数时,如果实际参数的位置空着不写,则使用这个默认值。这是通过在函数定义中使用赋值运算符来为参数赋值,调用函数如果没有传参,则默认使用该数值。若指定了值,则使用传递的值。

        使用方法:
实例一:不带默认值

#include <iostream>
using namespace std;


int fun(int a, int b, int c);

int main()
{
	fun(10, 20, 30);	//若有默认值,且调用时未传参,则push 默认值
	/*
	push 30		//
	push 20
	push 10
	call fun()
	add esp,0ch
	*/

	return 0;
}

int fun(int a, int b, int c)
{
	/*
	push abp
	mov ebp,esp
	sub esp,Occh
	……
	*/
	cout << a << '\n' << b << '\n' << c << endl;
	return 0;
	/*
	mov eax,0
	*/
}

实例二:函数声明与定义冲突

#include <iostream>
using namespace std;

int fun1(int a, int b, int c = 100);//定义时 c = 100,
int main()
{
	fun1(10, 20, 30);	//error 重定义默认参数: 参数 1

	return 0;
}

int fun1(int a, int b, int c = 100)// c = 100 缺省参数重定义
{
	cout << a << '\n' << b << '\n' << c << endl;
	return 0;
}
实例三:声明点和定义点同时赋值


#include <iostream>
using namespace std;


int fun2(int a, int b, int c = 100);//定义时 b = 100,支持缺省一位参数

int main()
{
	fun2(10, 20, 30);
	fun2(10, 20);
	//fun2(10);//	error 函数不接受 1 个参数
	/*
		错误原因:程序顺序执行,在调用时只能看见该函数之前的声明。因此,在
		函数定义点中的"b=100",处的默认参数无效。
	*/
	return 0;
}

int fun2(int a, int b = 100, int c)//编译通过,但 b 的默认赋值无用
{
	cout << a << '\n' << b << '\n' << c << endl;
	return 0;
}
实例四:多个声明中连续赋值

#include <iostream>
using namespace std;


int fun3(int a, int b, int c = 100);
int fun3(int a, int b = 100, int c);
int main()
{
	fun3(10, 20, 30);
	fun3(10, 20);	// 10 20  100
	fun3(10);		// 10 100 100
	return 0;
}

int fun3(int a, int b, int c)
{
	cout << a << '\n' << b << '\n' << c << endl;
	return 0;
}

        注意事项:          
  1. 从左到右原则
    如上所述,一旦某个参数被赋予默认值,其后的所有参数都必须有默认值。这是因为编译器需要能够区分哪些参数是明确提供的,哪些是默认提供的。

  2. 声明与定义
    默认参数值只能在函数声明中指定,而不能在函数定义中指定。如果同时有声明和定义,并且两者都试图为同一个参数指定默认值,这会导致编译错误。

  3. 头文件中的声明
    通常,函数的声明会放在头文件中,而定义则放在源文件(.cpp文件)中。因此,默认值也应该在头文件的函数声明中指定。

        2)函数的重载 

                函数的重载就是函数名相同,参数列表不同(参数的个数、类型、顺序),返回值没有要求。

#include <iostream>

using namespace std;

void swap(int *a, int *b);

void swap(float *a, float *b);

int main(void)
{
    int a1 = 0, b1 = 0;
    float a2 = 0, b2 = 0;

    a1 = 13, b1 = 7;
    a2 = 13.7, b2 = 7.31;
#if 0
    cout << "a1 = " << a1 << ", b1 = " << b1 << endl;

    swap(&a1, &b1);

    cout << "a1 = " << a1 << ", b1 = " << b1 << endl;
#else
    cout << "a2 = " << a2 << ", b2 = " << b2 << endl;

    swap(&a2, &b2);

    cout << "a2 = " << a2 << ", b2 = " << b2 << endl;
#endif
    return 0;
}

void swap(int *a, int *b)
{
    int c = 0;

    c = *a;
    *a = *b;
    *b = c;
}

void swap(float *a, float *b)
{
    float c = 0;

    c = *a;
    *a = *b;
    *b = c;
}

        函数重载是一种多态性的表现形式,有助于提高代码的可读性和可维护性,当编译器遇到同名函数时,会根据实际的参数列表来决定调用哪个函数,从而提高更严格的函数类型检查,减少程序的错误。 

        C语言不支持函数的重载,在之前使用的open(2)函数并不是函数的重载,而是可变参(不定参)函数。

5、引用

        引用相当于变量的别名,是某个变量的另一个名字,一旦把引用初始化为某个变量,就可以使用该引用名来指向这个变量。

#include <iostream>

using namespace std;

void swap(int *a, int *b);    // 使用指针实现

void swap(float *a, float *b);    

void swap(int &a, int &b);    // 使用引用实现

int main(void)
{
    int a1 = 0, b1 = 0;
    float a2 = 0, b2 = 0;

    a1 = 13, b1 = 7;
    a2 = 13.7, b2 = 7.31;
#if 0
    cout << "a1 = " << a1 << ", b1 = " << b1 << endl;

    swap(&a1, &b1);

    cout << "a1 = " << a1 << ", b1 = " << b1 << endl;
#else
    cout << "a2 = " << a2 << ", b2 = " << b2 << endl;

    swap(&a2, &b2);

    cout << "a2 = " << a2 << ", b2 = " << b2 << endl;
#endif

    cout << "a1 = " << a1 << ", b1 = " << b1 << endl;

    swap(a1, b1);

    cout << "a1 = " << a1 << ", b1 = " << b1 << endl;

    return 0;
}

void swap(int *a, int *b)
{
    int c = 0;

    c = *a;
    *a = *b;
    *b = c;
}

void swap(float *a, float *b)
{
    float c = 0;

    c = *a;
    *a = *b;
    *b = c;
}

void swap(int &a, int &b)
{
    int c = 0;

    c = a;
    a = b;
    b = c;
}

-----------------------------------------------------------------------------------------

三种变量互换的实现

使用加减运算使用异或运算使用第三个变量

void swap (int &a, int &b)

{

        a = a + b;

        b = a - b;

        a = a - b;

}

void swap (int &a, int &b)

{

        a = a ^ b;

        b = a ^ b;

        a = a ^ b;

}

void swap (int &a, int &b)

{

        int c = a;

        a = b;

        b = c;

}

-----------------------------------------------------------------------------------------

        我们可以将变量名想象成是变量在内存位置中的标签,那么引用就是变量在内存中的第二个标签,因此我们可以通过引用来访问形参的内容。

        引用类似于指针,但并不是指针,它们的不同:

【1】表达

        指针是用来存储地址的

        int i = 100;        int *p = &i;

        引用就是变量本身

        int i = 100;        int &p = i;

【2】占用空间

        指针占用存储空间,在64bit的环境中占用8byte,在32bit的环境中占用4byte

        引用不占用存储空间,就是一个别名

【3】空指针和空引用

        程序中可以存在空指针(指向NULL的指针就是空指针)

int *p = NULL;    // 定义一个空指针

        程序中不可以存在空引用,引用必须连接到一块合法的内存中(一个存在的变量名)

【4】指向对象

        指针(普通指针)可以在任何时候指向另一个对象,一旦引用被初始化为一个对象,就不能引用到另一个对象(有点像指针常量)

【5】初始化

        指针可以在任何时候被初始化

        引用必须在创建时被初始化

-----------------------------------------------------------------------------------------

指针与数据类型

问题:
a) 一个整型数(An integer) 
b)一个指向整型数的指针( A pointer to an integer) 
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r 
d)一个有10个整型数的数组( An array of 10 integers) 
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers) 
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers) 
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) 
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
i) 一个函数,该函数的参数是整数类型,该函数的返回值是指针。

答案:
a)int p;
b)int *p;
c)int **p;
d)int p[10];
e)int (*p[10]);
f)int (*p)[10];
g)int (*p)(int);
h)int (*p[10])(int);
i)int *p(int);

思考:
上面的问题和答案是可以反过来问的
再基于答案反推一遍,请描述上述数据类型的含义:
a)一个整数类型。
b)一个指针,指向整数类型。
c)一个指针,指向另一个指针,它指向的指针指向一个整数类型。
d)一个数组,它包含10个整数类型。
e)一个数组,它包含10个指针类型,指针指向整数类型。
f)一个指针,它指向一个数组,它包含10个整数类型。
g)一个指针,指向一个函数,该函数的参数是整数类型,该函数的返回值是整数类型。【函数指针】
h)一个数组,它包含10个指针类型,指针指向一个函数,该函数的参数是整数类型,该函数的返回值是整数类型。
i) 一个函数,该函数的参数是整数类型,该函数的返回值是指针,指向一个整数类型。【指针函数】

 指针与关键字

将指针和const关键字相结合,就有了如下的题目:
a)char * const p;
b)char const *p;
c)const char *p;
d) char const* const p;
问题:请问上述三种数据类型的含义。 

解答:
a)一个指针,指向char数据类型,p是常量。【指针不可更改】
b)一个指针,指向char数据类型,p指向的数据是常量。【所指的内容不可更改】
c)一个指针,指向char数据类型,p指向的数据是常量。(char放在前后都没关系)
d)一个指针,指向char数据类型,p是常量,p指向的数据也是常量。【指针和所指向的数据都不可更改】

思考:
试试总结一下static关键字与指针的关系。

 指针与数组

将指针和数组相结合,就有了如下的题目:
char *p="abcdef";
char a[]="abcdef";
问题:请问p[i]与a[i]的区别?

解答:
首先,"abcdef"是一个字符串,在编译的时候,字符串被放在常量区。(挖坑,C:请描述一个可执行程序占用的内存分为哪几个区?每个分区各自的作用?)
*p是指针,指向常量区的字符串。
a[]是数组,在程序执行时,会从常量区拷贝"abcdef"到栈上,再继续后续的操作。
因此,这两个变量的指向的内存区域不同,打印的内存地址不同。

思考:
在上述题目的基础上,加上如下代码,编译是否会出错?为什么?
p[1]='h';
a[1]='h';
【提示:常量区数据无法修改】

 指针、数组、关键字

上面的确定都会了?
将指针和关键字结合,再和数组结合,试试看做这题:
char a[]="abcdef";
char b[]="abcdef";
const char c[]="abcdef";
const char d[]="abcdef";
char *e="abcdef";
char *f="abcdef";
const char *g="abcdef";
const char *h="abcdef";
问题:
a,b地址是否相等?
c,d地址是否相等?
e,f地址是否相等?
g,h地址是否相等?

答案:
a!=b;
c!=d;
e==f;
g==h;

解答:
e,f,g,h都是指针,指向常量区的字符串,该字符串在内存中的地址是固定的。
a,b,c,d都是数组,在程序执行时,从常量区拷贝数据到栈中,每个数组的在栈中都会被分配地址。

 指针函数与函数指针

C:什么是指针函数?什么是函数指针?他们之间有什么区别?
【快速记忆法:末尾是什么,他就是什么类型】
指针函数:一个函数,它的返回值是指针。
函数指针:一个指针,它指向一个函数。

【函数指针】定义形式:
    类型 (*指针变量名)(参数列表);
例如:
    int (*p)(int i,int j);
说明:
    p是一个指针,它指向一个函数,该函数有2个整形参数,返回类型为int。
    p首先和*结合,表明p是一个指针。然后再与()结合,
    表明它指向的是一个函数。指向函数的指针也称为函数指针。

【指针函数】定义形式:
    类型 *指针变量名 (参数列表):
例如:
    int *p(int i, int j);
说明:
    p是一个函数,该函数有2个整形参数,它的返回值是一个指针,指向一个整数类型。
    因为()的优先级比*更高,所以可以写成:int *(p(int i,int j)),
    表明它是一个函数,返回值是一个指向整型的指针。

-----------------------------------------------------------------------------------------

三、 面向对象

1、有关C语言的编程思想

        面向过程(procedure oriented programming)简称POP的编程语言

        C语言项目工程:

        [1]划分模块

        [2]每一个模块定义数据类型 + 函数接口

        [3]实现函数功能

        [4]通过调用函数最终实现项目功能

这是典型的面向过程的编程思想,分析出解决问题所需要的步骤,用函数将这些步骤一步一步实现,使用的时候,一个一个进行调用。

2、有关C++语言的编程思想

        面向对象(object oriented programming)简称OOP的编程语言

        C++项目工程:

        [1]划分模块

        [2]抽象类,类中有各种数据类型和函数

        [3]通过实例化对象,驱动对象的函数

        [4]最后实现项目工程

这是典型的面向对象的编程思想,把构成文件的各个事务分解成各个对象,实例化对象的目的不是为了完成一个步骤,而是为了描述一个事务在整个解决问题的步骤中的行为。

3、例子对比

        1)五子棋游戏

面向过程

面向对象

1、画棋盘

2、player1下棋

3、画棋盘

4、判断输赢

5、player2下棋

6、画棋盘

7、判断输赢

------循环------

1、玩家类

实例化两个对象player1和player2,

下棋

2、棋盘类

实例化一个对象,

画棋盘

3、规则类

实例化一个对象,

判断输赢

        2)洗衣服
面向过程面向对象

1、放衣服

2、放洗衣液

3、放水

4、开始洗衣

5、甩干

6、取衣服

1、人类

放衣服、放洗衣液、取衣服

2、洗衣机类

放水、开始洗衣、甩干

        3)史书

面向过程比喻为编年体(主要以时间为主线,按照年月日的顺序来记述历史事件)            --- 春秋

面向对象比喻为纪传体(主要以人物为中心,通过记述各种人物的活动来反映历史事件) --- 史记 

-----------------------------------------------------------------------------------------

各专有名词的区别

匿名管道-----命名管道

进程间通信(IPC)

        每个进程有各自不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到。所以进程之间交换数据,需要依靠内核(kernel),在内核开辟一块缓存区,进程1把数据从用户空间拷贝到缓存区,进程2再从缓存区把数据读走。内核提供的这种机制就是进程间通信。

匿名管道(pipe):

        管道是IPC最基本的一种实现机制,在Linux下,“一切皆文件”,这里的管道就是一个文件。管道实现进程通信就是让两个进程都能访问该文件。

        管道的特征:

  1. 只提供单向通信(一方读,一方写)
  2. 只能用于具有血缘关系的进程间通信(父子进程、兄弟进程)
  3. 管道是基于字节流来通信的
  4. 依赖于文件系统,生命周期随进程结束
  5. 机制本身带同步互斥效果

        实现方式:

                创建管道:

int pipe(int pipefd[2])

                        注释:调用pipe函数时,首先在内核中开辟一块缓冲区用于通信,有一个读端和写端,通过pipifd参数传出给用户进程两个文件描述符,pipefd[0]指向读端,pipefd[1]指向写端。在用户层面,打开管道就是打开了一个文件,通过read(2)读数据,write(2)写数据。

                        头文件:<unistd.h>

                        返回值:成功返回0,失败返回-1,设置errno

                创建子进程:

pid_t fork(void);

                        注释: 调用fork函数时,程序会创建一个子进程,将父进程完全拷贝一份,包括当前程序运行到的位置,使程序进行并发运行,具体谁先谁后,要看处理器的调度策略。

                        头文件:<sys/types.h>、<unistd.h>

                        返回值:成功返回子进程的pid号,为0;失败返回-1,并设置errno;

        实现步骤:

                1】调用pipe函数,有父进程创建管道,得到两个文件描述符指向管道的两端

                2】父进程调用fork创建子进程,通过拷贝,子进程也有两个文件描述符指向管道的两端

                3】父进程关闭读端(0),只进行写操作;子进程关闭写端(1)只进行读操作,管道是通过唤醒队列实现的,数据从写端流入读端,从而实现进程间通信。

        注意事项:

                1】如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么文件内的所有内容被读完后再次read就会返回0,就像读到文件结尾。

                2】如果有指向管道写端的文件描述符没有关闭(写端的引用计数大于0),而持有管道写端的进程没有向管道内写入数据,如果这时有进程从管道读端读数据,那么读完管道内剩余的数据后会阻塞等待,直到有数据可读才读取数据并返回。

                3】如果所有指向管道读端的文件描述符都关闭,此时有进程通过写端文件描述符向管道内写数据时,进程会收到SIGPIPE信号,并异常终止。

                4】如果有指向管道读端的文件描述符没有关闭(读端的引用计数大于0),而持有管道读端的进程没有从管道内读数据,如果这时有进程向管道写端写数据,那么管道被写满后会阻塞等待,直到管道内有空位置后才写入数据并返回。

命名管道(FIFO)

        它提供了一个路径名与之关联,以FIFO文件的形式存储在文件系统中,可以实现任意两个进程之间的通信,而匿名管道对于文件系统是不可见的,它仅限于在父子进程之间的通信。

        FIFO是一个设备文件,在文件系统中以文件名的形式存在,因此即使进程与创建FIFO的进程不存在血缘关系也依然可以通信,前提是可以访问该路径。

        FIFO(first input first output)总是总是遵循先进先出的原则,第一个进来的数据会被第一个读走。

        实现方式(两种):

                1、在shell下使用命令mknod或mkfifo创建命名管道,进程之间使用read\write进行通信

                2、系统函数创建

        

                                                                             

报式套接字-----流式套接字

文件io-----系统io

II2C-----SPI

uart-----usart

重入------重载

-----------------------------------------------------------------------------------------

任务

1、了解RTOS(FreeRTOS / RTThread)

        FreeRTOS:
        RTThread:

2、了解STM32CubeMx工具

3、排序算法

        1)冒泡排序(Bubble Sort)

              它依次遍历要排序的元素序列,比较相邻两个元素,如果顺序不满足要求,就把它们的位置互换,直到遍历完序列中所有元素均满足要求,则说明该元素序列 已经排序完成,程序退出。

              因为在进行排序时,越小的元素(降序排列)会经由交换,慢慢的“浮”到数列的顶端,就像碳酸饮料的二氧化碳气泡慢慢浮起,故名“冒泡排序”。

#include <iostream>

#define LEN    15
using namespace std;

void swap(int &a, int &b);
void bubble_sort(int arr[], int len);
void bubble_sort_pro(int arr[], int len);

int main(void)
{
    
    return 0;
}

void swap(int &a, int &b)
{
    int tmp = 0;
    tmp = a;
    a = b;
    b = tmp;
}

void bubble_sort(int arr[], int len)
{
    int i = 0, j = 0;
    for (; i < len - 1; i ++) {
        for (; j < len - i - 1; j ++) {
            if (arr[j] < arr[j + 1])    // 降序排列
                swap(arr[j], arr[j + 1]);
        }
    }
}

// 冒泡优化,添加标志位,避免不必要的比较
void bubble_sort_pro(int arr[], int len)
{
    int i = 0, j = 0;
    bool flag = true;
    for (; i < len - 1; i ++) {
        flag = true;
        for (; j < len - i - 1; j ++) {
            if (arr[j] < arr[j + 1]) {   // 降序排列
                swap(arr[j], arr[j + 1]);
                flag = false;
            }
        }
        if (flag)
            break;
    }
}
        2)选择排序(Selection Sort)

                它在每一趟从待排序列的元素中选出最小(或最大)的一个元素,放在待排序列的最前(或最后),直到全部待排序列的数据元素排列完毕。

#include <iostream>

#define LEN    15
using namespace std;

void swap(int &a, int &b);
void selection_sort(int arr[], int n);

int main(void)
{
    
    return 0;
}

void swap(int &a, int &b)
{
    int tmp = 0;
    tmp = a;
    a = b;
    b = tmp;
}

void selection_sort(int arr[], int n)
{
    int min = 0;
    int i, j;
    for (i = 0; i < n - 1; i ++) {
        min = i;
        for(j = i; j < n; j ++) {
            if (arr[min] > arr[j])
                min = j;
        }
        if (min != i)
            swap(arr[min], arr[j]);
    }
}

        3)插入排序(Insertion Sort)

                它通过构建一个有序序列实现排序,对于未排序的数据,它将这个数据与前面的数据进行比较,找到相应的位置进行插入,直到最后一个数据排序完成。

#include <iostream>

#define LEN    15
using namespace std;

void insertion_sort(int arr[], int n);

int main(void)
{
    
    return 0;
}

void insertion_sort(int arr[], int n)
{
    int i = 0, j = 0;
    int sentry = 0;    //    哨兵(存储需要改变位置的元素值)

    for (i = 1; i < n; i ++) {
        if (arr[i] < arr[i - 1]) {
            sentry = arr[i];
            j = i - 1;
            // 升序排列
            while (sentry < arr[j] && j >= 0) {
                arr[j + 1] = arr[j];
                j --;
            }
            arr[j + 1] = sentry;
        }
    }
}

        4)希尔排序(Shell Sort)

                它是简单插入排序的改进版,也称为缩小增量排序,它先选定一个跨度(增量),将待排序列按照跨度(增量)分割为若干子序列,分别进行直接插入排序,跨度(增量)每次缩小,直到缩小为1,就变为对整个序列进行直接插入排序,这时需要进行替换的元素变得很少,大部分只是比较操作,一定程度上提高了排序效率。

#include <iostream>

#define LEN    15
using namespace std;

void shell_sort(int arr[], int n);

int main(void)
{
    
    return 0;
}

void shell_sort(int arr[], int n)
{
    int i = 0, j = 0, k = 0;
    int sentry = 0;    //    哨兵(存储需要改变位置的元素值)
    int gap = 0;    //    跨度(增量)

    for (gap = n / 2; gap > 0; gap /= 2) {
        for (i = gap; i < n; i ++) {
            sentry = arr[i];
            j = i - gap;
            // 升序排列
            while (sentry < arr[j] && j >= 0) {
                arr[j + gap] = arr[j];
                j -= gap;
            }
            arr[j + gap] = sentry;
        }
    }
}

         5)归并排序(Merge Sort)

                它采用分治法(Divide and Conquer)的思想,将待排序列先分为长度为n/2的子序列,分别进行归并排序,最后将两个子序列合并成一个最终的有序序列。

#include <iostream>

#define LEN    15
using namespace std;

void merge(int arr[], int n);
void merge_sort(int arr[], int n);

int main(void)
{
    
    return 0;
}

void merge(int arr[], int n)
{
    int tmp[n] = {};
    int pos = 0;    // 存储元素的位置
    int mid = n / 2;
    int first = 0, second = mid;    // 有序序列的起始位置
    int i = 0;

    while (first < mid && second < n) {
        if (arr[first] < arr[second])
            tmp[pos ++] = arr[first ++];    
        else
            tmp[pos ++] = arr[second ++];
    }

    while (first < mid)
        tmp[pos ++] = arr[first ++];
    while (second < n)
        tmp[pos ++] = arr[second ++];
    for (i = 0; i < n; i ++)    // 将排好序的子序列合并为一个有序序列
        arr[i] = tmp[i];
}

void merge_sort(int arr[], int n)
{
    if (n > 1) {
        merge_sort(arr, n / 2);
        merge_sort(arr + n / 2, n - n / 2);
        merge(arr, n);
    } else {
        return ;
    }
}

        6)快速排序(Quick Sort)

                每次选择一个基准值,将小于基准值的放前边,将大于基准值的放后边,再对两边重新选择基准值,递归实现。

#include <iostream>

#define LEN    15
using namespace std;

void partition(int arr[], int left, int right);
void quick_sort(int arr[], int start, int end);

int main(void)
{
    
    return 0;
}

void partition(int arr[], int left, int right)
{
    int key = arr[left];
    int pos = left;
        
    while (left < right) {
        while (left < right && arr[right] >= key)
            right --;

        arr[pos] = arr[right];
        pos = right;

        while (left < right && arr[left] <= key)
            left ++;

        arr[pos] = arr[left];
        pos = left;        
    }
    arr[pos] = key;

    return pos;
}

void quick_sort(int arr[], int start, int end)
{
    if (start >= end) {
        return ;
    }

    int pos = partition(arr, start, end);
    
    quick_sort(arr, start, pos - 1);
    quick_sort(arr, pos + 1, end);
}
各排序算法对比 

           

                

             

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值