victory的博客

长安一片月,万户捣衣声

0%

Python是否支持多线程

  1. 多线程

    线程在CPU上运行,是操作系统进行调度和执行的基本单位

​ 首先,要明确进程与线程的关系。一个进程可以包含多个线程,而每个线程都是进程中的一个执行流。可以把进程比作一个工厂,而线程就像是工厂里的工人,它们共同协作完成工作。

​ 其次,线程的创建和运行是在一个进程中进行的。当启动一个线程时(例如,通过调用Thread.start()方法),操作系统会为这个线程分配必要的资源,包括寄存器、栈等,并在CPU上执行该线程的代码。

​ 再者,在多核CPU系统中,不同的线程可以被分配到不同的核心上并行运行。而在单核CPU中,线程的运行会通过时间片轮转或优先级调度等方式进行切换,从而实现并发执行。

​ 最后,线程之间共享所属进程的内存空间,包括代码段、数据段、堆和栈。这意味着线程可以访问相同的变量和数据结构,但同时需要注意同步机制来避免数据竞争和不一致的问题。

综上所述,线程是在CPU上运行的,它们利用进程中的资源和内存空间,按照操作系统的调度策略执行任务。

  1. python是否支持多线程?

Python支持多线程,但由于全局解释器锁(GIL)的存在,在任意时刻只能有一个线程执行Python字节码。Python中的多线程是通过在一个进程中创建多个线程来实现的,每个线程可以执行不同的任务。然而,由于Python解释器(cpython,由c语言编写的python解释器库)的GIL限制,这些线程并不是真正意义上的并行执行。GIL确保了同一时间只有一个线程能够访问Python的对象和内存空间,这意味着即使在多核处理器上,Python的多线程也无法实现真正的并行计算。

​ 尽管如此,Python的多线程对于I/O密集型任务仍然非常有用,因为它们可以在等待I/O操作完成时释放GIL,从而允许其他线程执行。在这种情况下,多线程可以提高程序的效率,因为它们可以减少因等待I/O而浪费的时间。

​ 此外,如果需要执行CPU密集型任务,或者希望充分利用多核处理器的能力,可以考虑使用Python的多进程模块multiprocessing。多进程可以绕过GIL的限制,因为每个进程都有自己的Python解释器和内存空间,从而实现真正的并行执行。

​ multiprocessing实现多进程并行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import multiprocessing 

def print_cube(num):
print("Cube: {}".format(num * num * num))

def print_square(num):
print("Square: {}".format(num * num))

if __name__ == "__main__":
# 创建进程
p1 = multiprocessing.Process(target=print_square, args=(10, ))
p2 = multiprocessing.Process(target=print_cube, args=(10, ))

# 开启进程
p1.start()
p2.start()

# 等待p1、p2线程都执行完
p1.join()
p2.join()

print("Done!")

C++字符串分割

字符串分割是编程中经常遇到的一个问题,采用C++语言实现字符串分割与其他语言有所不同,下面将采用C++语言实现字符串分割并对用到的字符串操作函数进行详细的描述。

  • 字符串分割Java实现

    Java通过调用String.split(String delimiter)方法可直接完成对字符串的分割。

    1
    2
    3
    4
    5
    6
    7
    public static void  main(String[] args){
    String str = "I am a good programmer!";
    String[] splited_string = str.split(" ");
    for(String s : splited_string){
    System.out.println(s);
    }
    }
  • 字符串分割Python实现

    python实现字符串分割代码更为间接,很多功能使用python实现仅用一行代码,江湖上称之为“一行流”。

    1
    print("I am a good programmer!".split(" "))
  • 字符串分割C++实现

    比起Java和python实现,采用C++实现字符串分割的过程则略为繁琐。需要用到c_str()、strcpy()、strtok()等方法。

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <cstring>
    #include <string>
    #include <vector>
    #include <iostream>

    using namespace std;

    void splitString(const string &str, const string &split, vector<string> &res)
    {
    // str:要处理的字符串
    // split:分隔符
    // res:存放分割后的结果
    char *strc = new char[str.size() + 1];

    strcpy(strc, str.c_str());

    char *temp = strtok(strc, split.c_str());
    //strtok()的源码为char *__cdecl strtok(char * __restrict__ _Str,const char * __restrict__ _Delim)
    //strtok接收的参数时char*类型,因此进行字符串分割前需要将string类型的字符串转为char*类型

    while (temp != NULL)
    {
    res.push_back(string(temp));
    temp = strtok(NULL, split.c_str()); //strtok第一个参数传入NULL,使用之前保存的SAVE_PTR定位下一个待处理的字符的位置
    }
    delete[] strc;
    }

    int main(){
    vector<string> result;
    splitString("I am a good programmer!", " ", result);

    cout << "分割的子字符串的数量为:" << result.size() << endl;
    cout << "分割后的字符串为:" << endl;
    for(int i = 0; i < result.size(); ++i) {
    std::cout << result[i] << endl;
    }

    return 0;
    }
  • 字符串分割所使用方法详解

    • string.c_str()

      c_str()方法的原型如下:

      1
      const _CharT* c_str()

      该方法返回一个指向常量的指针,指针所指向的内容不能被修改,在字符串分割中主要使用该方法将string类型转为char*类型,与字符串分割函数strtok()方法的接收参数类型保持一致。注意:一定要使用strcpy()函数等来操作方法c_str()返回的指针。

    • string.strcpy()

      strcpy()方法的原型如下:

      1
      char * __cdecl strcpy(char * __restrict__ _Dest,const char * __restrict__ _Source);

      该方法将字符串Source复制到Dest。该方法线程不安全,c++提供了线程安全的字符串拷贝方法strcpy_s()。

    • string.strtok()

      strtok()方法的原型如下:

      1
      char *__cdecl strtok(char * __restrict__ _Str,const char * __restrict__ _Delim)

      其中,Str表示要进行分割的字符串,Delim为字符串分隔符,该方法将Delim中的字符作为分隔符对字符串Str进行分割。如果Str为空,则函数内部的SAVE_PTR指针在下一次调用中将作为下一个分割字符串的起始位置。其实这就说明在函数strtok()内部使用了一个静态变量SAVE_PTR指针,用以记录分割一次之后_String中下一个字符串的位置。这种方法导致了一个问题,就是strtok()函数线程不安全的(因为其函数内部使用到了静态存储区)。

      除此之外,从函数的定义,第一个传入参数_String定义为char*而不是const char*,就说明strtok()函数不保证不修改传入数据的内容。实际上,第一个参数_String传进来的字符串,是会被strtok()函数所修改的,因此调用strtok()函数的时候应当注意。

      c++中也提供了线程安全的字符串分割方法strtok_s,其原型如下:

      1
      char *__cdecl strtok_s(char *_Str,const char *_Delim,char **_Context);

      strtok_s()函数增加了一个参数_Context,这个参数就是相当于strtok()函数中内部定义的静态SAVE_PTR指针,用来传递对字符串_String的处理进行到了哪里。

参考文章:C/C++——字符串分割(strtok, strtok_s)

C++ OOP方法实现链式队列

  • 队列

    队列(Queue)是一个先进先出(First In First Out, FIFO)的数据结构,即先入队的元素先出队。队列有两种存储形式:顺序存储和链式存储,采用顺序存储方式的队列称为顺序队列,采用链式存储的队列称为链式队列。顺序队列采用数组存储队列中的元素,由队头指针head和队尾指针tail表示队列的头尾。链式队列采用链表实现,由头结点和若干个队列元素节点组成,头结点包括队头指针head、队尾指针和队列大小size三个域,head指针指向队头,tail指针指向队尾,队列元素节点由值域val和指针域next组成,val代表队列元素的值,next代表指向下一个队列结点的指针。其中,较为常用的是链式队列,下面将详细介绍链式队列的属性、方法以及C++代码实现。

  • 队列属性和方法介绍

    对队列进行抽象,可将其表示为包括队头指针、队尾指针和队列大小三个属性的数据结构。队列的基本操作包括入队、出队、队列判空、队列销毁、队列元素输出等,其伪代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    //队列定义
    queue:
    head, tail, size//head:队头指针、tail:队尾指针、size:队列大小
    enqueue(elem)//入队
    dequeue()//出队
    is_empty()//队列判空
    destory()//队列销毁
    traverse()//队列元素输出
  • 链式队列C++实现

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    #include<iostream>

    using namespace std;

    //队列中的元素结点
    class QueueNode{
    public:
    int val;//值域
    QueueNode *next;//指针域,指向下一个队列元素
    };

    //队列
    class Queue{
    private:
    int size;//队列大小

    public:
    QueueNode *head;//队头指针
    QueueNode *tail;//队尾指针

    Queue();//构造函数,完成对象初始化工作
    ~Queue();//析构函数
    void enqueue(int val);//入队
    int dequeue();//出队
    bool is_empty();//队列判空
    void destory();//销毁队列
    void traverse();//输出队列中的所有元素
    int get_size();//获取队列大小
    };

    //获取队列大小
    int Queue::get_size(){
    return this->size;
    }

    //构造函数
    Queue::Queue(){
    this->head = this->tail = NULL;
    this->size = 0;
    }

    //析构函数
    Queue::~Queue(){

    }

    //入队
    void Queue::enqueue(int val){
    QueueNode *node = (QueueNode *)malloc(sizeof(QueueNode));

    node->val = val;
    node->next = NULL;

    if(this->head == NULL){
    this->head = node;
    this->tail = node;
    }
    else{
    this->tail->next = node;
    this->tail = node;
    }

    this->size += 1;
    };

    //出队
    int Queue::dequeue(){
    if(this->is_empty() == 1){
    cout << "队列为空,没有元素可以出队,请先向队列中添加元素!" << endl;
    return -1;
    }

    int val = this->head->val;
    this->head = this->head->next;

    this->size -= 1;

    return val;
    }

    //队列判空
    bool Queue::is_empty(){
    if(this->size == 0){
    return true;
    }
    else
    {
    return false;
    }
    }

    //输出所有队列元素
    void Queue::traverse(){
    if(this->is_empty() == 1){
    cout << "队列为空!" << endl;
    return;
    }

    cout << "队列所有元素如下:" << endl;

    QueueNode *p_node = this->head;

    while(p_node != NULL)
    {
    cout << p_node->val << endl;
    p_node = p_node->next;
    }
    }

    //销毁队列
    void Queue::destory(){
    if(this->is_empty() == 1){
    cout << "队列中没有元素!" << endl;
    return;
    }
    int val = this->dequeue();
    while(val != -1){
    val = this->dequeue();
    }
    cout << "queue is destroyed!" << endl;

    }

    //主函数
    int main(){
    //队列定义
    Queue queue;

    //操作队列
    queue.enqueue(1);//元素入队列
    queue.enqueue(2);
    queue.enqueue(3);
    queue.enqueue(4);
    queue.enqueue(5);
    int val = queue.dequeue();//元素出队列
    cout << "出队元素为:" << val << endl;
    queue.traverse();//输出所有队列元素
    queue.destory();//销毁队列
    queue.traverse();
    cout << "queue.size:" << queue.get_size() << endl;//输出队列大小
    cout << queue.is_empty() << endl;//队列判空

    return 0;
    }

参考资料:队列的基本操作(顺序队列、循环队列、链式队列)

lambda表达式

  1. lambda表达式是什么?

    Lambda 表达式(通常称为 Lambda)是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象(闭包)的简便方法。

  2. lambda表达式的语法

    1
    [capture list] (parameters) mutable throw() -> return-type {statement}
阅读全文 »

c++二级指针

  • 普通变量作为形参

    在函数中采用普通变量接收实参传递的变量,对变量的操作是对实参拷贝值的操作,不会改变原有变量的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include<iostream>

    using namespace std;

    void change_value(int a){
    a = 10;
    }

    int main(){
    int a = 10;
    cout << "before changed,a=" << a << endl;//输出:before changed,a=10
    change_value(a);
    cout << "after changed,a=" << a << endl;//输出:after changed,a=10
    return 0;
    }
  • 指针变量作为形参

    由上可知,普通变量作为形参时,对变量的操作其实是对拷贝值的操作,不会改变原来变量的值,若要改变原来变量的值,可采用指针变量作为形参。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include<iostream>

    using namespace std;

    void change_value(int *p){
    *p = 20;
    }

    int main(){
    int a = 10;

    cout << "before changed,a=" << a << endl;//输出:before changed,a=10
    change_value(&a);
    cout << "after changed,a=" << a << endl;//输出:after changed,a=20

    return 0;
    }
  • 二级指针作为形参

    以上是通过函数修改普通变量的值,若要对指针变量的指向进行修改,则需要使用二级指针来实现。

    首先在函数中,采用指针变量作为形参接收实参传递的指针,示例代码如下,由代码输出结果可知,用指针变量作为形参不能够修改原来指针的指向。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include<iostream>

    using namespace std;

    void change_value(int *p){
    p = new int(20);
    }

    int main(){
    int a = 10;
    int *p = &a;

    cout << "before changed,*p=" << *p << endl;//before changed,*p=10
    change_value(p);
    cout << "after changed,*p=" << *p << endl;//after changed,*p=10

    return 0;
    }

    以下代码是二级指针作为形参的示例代码,由代码输出可知,二级指针作为形参时可以修改原来指针变量的指向。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include<iostream>

    using namespace std;

    void change_value(int **p){
    *p = new int(20);
    }

    int main(){
    int a = 10;
    int *p = &a;
    int **pp = &p;
    cout << "before changed,*p" << *p << endl;
    change_value(pp);
    cout << "before changed,*p" << *p << endl;
    cout << "after changed,**pp" << **pp << endl;

    return 0;
    }

此外,二级指针还常与结构体、数组一起使用,二级指针搭配结构体/数组的使用将在后续补充 !

C++指针与引用

学习c++时,很容易混淆指针和引用的用法,以及对指针与引用的使用存在些许疑惑,下面通过示例代码说明指针和引用的区别。

  • 指针与引用的区别?
  1. 定义和性质不同

指针是一个变量,其存储的是一个地址,该地址指向内存的一个存储单元;举例,张三的身份证看做一个指针,这个指针指向了张三,张三的妈妈夸奖了身份证不等于张三的妈妈夸奖了张三。

引用是一个变量的别名,跟原来的变量实质上是同一个东西,类似于日常生活中“小名”的意思,例如张三的小名叫毛蛋,张三与毛蛋都指的是张三这个人,张三的妈妈夸奖了张三等于张三的妈妈夸奖了毛蛋。

1
2
3
int a = 996;
int *p = &a; // p是指针, &在此是求地址运算
int &r = a; // r是引用, &在此起标识作用

上面定义了一个整型变量 a,p 是一个指针变量,p 的值是变量 a 的地址;

而引用 r,是 a 的一个别名,在内存中 r 和 a 占有同一个存储单元。

阅读全文 »

shell脚本

  • shell脚本可将环境变量添加、依赖安装、二进制文件执行等命令包含在一个后缀为.sh的文本文件中,从而便于对应用的自动化运维

  • 常见的shell脚本语法

    1. 向窗口输出文本
    1
    echo "hello world"
    1. for loop
    1
    2
    3
    for file in `ls /etc`;do
    echo $file
    done
    1. 变量定义以及使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      # 输出变量内容
      word="this is a string!"
      echo $word
      echo "word变量的内容是${word}。"

      # 只读变量
      my_name="liao sheng li"
      readonly my_name
      echo $my_name
      my_name="sheng li" # ./test.sh:行20: my_name:只读变量

      # 删除变量
      username="zhang san"
      echo $username
      unset username
      echo $suername # 不会输出内容
    2. shell字符串

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      # 输出字符串
      echo "hello" # 双引号方式表示字符串
      echo 'hello' # 单引号方式表示字符串
      echo hello # 不使用引号方式

      # 拼接字符串
      echo "hello,"world"!"
      echo 'hello,'world'!'

      # 获取字符串长度
      string="hello"
      echo "字符串的长度为:${#string}"
      echo "字符串的长度为:${#string[0]}"

      # 提取子字符串
      string1="the sky is blue!"
      echo ${string1:4:3}

      # 查找子字符串
      string2="this is a long string!"
      echo `expr index "$string2" is`
  1. shell数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 定义数组
    arr1=(1 2 3 4 5 6 7 8 9 10)

    # 读取数组
    echo ${arr1}
    elem1=${arr1[0]} # 输出下表为0时的数组元素
    echo $elem1
    echo ${arr1[@]} #输出数组所有元素

    # 获取数组的长度
    echo ${#arr1[@]} # 输出数组长度
    echo ${#arr1[*]} # 输出数组长度
    echo ${#arr1[0]} #输出下表为0的数组元素的长度

    # 关联数组
    declare -A site1=(["google"]="www.google.com" ["runoob"]="www.runoob.com" ["taobao"]="www.taobao.com")
    echo ${site1["google"]}
    echo ${site1[@]}
    declare -A site2
    site2["google"]="www.google.com"
    site2["runoob"]="www.runoob.com"
    site2["taobao"]="www.taobao.com"
    echo ${site2["taobao"]}
    echo ${site2[*]}
    1. shell注释
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 这是一个单行注释

    : <<COMMENT
    这是
    一个
    多行注释!
    COMMENT

    : '
    这是一个
    多行注释
    '
  2. shell传递参数

    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
    echo "此处执行shell脚本的命令为./test.sh 1 2 3"
    echo "执行的文件名:$0"
    echo "执行的文件名:$1"
    echo "执行的文件名:$2"
    echo "执行的文件名:$3"
    : '
    执行结果:
    执行的文件名:./test.sh
    执行的文件名:1
    执行的文件名:2
    执行的文件名:3
    '
    # 处理参数的集中特殊字符
    echo $# # 输出传递给脚本的参数个数,执行脚本./test.sh 1 2 3输出3
    echo $* # 以单个字符串显示所有传递个脚本的参数就,执行脚本./test.sh 1 2 3输出1 2 3
    echo $$ # 输出脚本运行的当前进行ID号
    echo $@ # 与$*相同
    echo $-
    echo $? #显示最后命令的退出状态,0表示没有错误,其他任何值表明有错误

    # $*与$@的区别
    # $*
    for i in "$*"; do
    echo $i
    done
    # $@
    for i in "$@"; do
    echo $i
    done
  3. shell运算符

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    # 算数运算符
    a=10
    b=20
    val=`expr $a + $b`
    echo "a + b : $val"
    val=`expr $a - $b`
    echo "a - b : $val"
    val=`expr $a \* $b`
    echo "a * b : $val"
    val=`expr $b / $a`
    echo "b / a : $val"
    val=`expr $b % $a`
    echo "b % a : $val"
    if [ $a == $b ]
    then
    echo "a 等于 b"
    fi
    if [ $a != $b ]
    then
    echo "a 不等于 b"
    fi

    # 关系运算符
    a=10
    b=20
    if [ $a -eq $b ] # 判断两个数字的值是否相等
    then
    echo "$a -eq $b : a 等于 b"
    else
    echo "$a -eq $b: a 不等于 b"
    fi
    if [ $a -ne $b ] # 判断两个数字的值是否不相等
    then
    echo "$a -ne $b: a 不等于 b"
    else
    echo "$a -ne $b : a 等于 b"
    fi
    if [ $a -gt $b ] # 判断一个数字是否大于另一个数字
    then
    echo "$a -gt $b: a 大于 b"
    else
    echo "$a -gt $b: a 不大于 b"
    fi
    if [ $a -lt $b ] # 判断一个数字是否小于另一个数字
    then
    echo "$a -lt $b: a 小于 b"
    else
    echo "$a -lt $b: a 不小于 b"
    fi
    if [ $a -ge $b ] # 判断一个数字是否大于小于另一个数字
    then
    echo "$a -ge $b: a 大于或等于 b"
    else
    echo "$a -ge $b: a 小于 b"
    fi
    if [ $a -le $b ] # 判断一个数字是否小于等于另一个数字
    then
    echo "$a -le $b: a 小于或等于 b"
    else
    echo "$a -le $b: a 大于 b"
    fi

    # 布尔运算符
    a=10
    b=20
    if [ $a != $b ] # 非
    then
    echo "$a != $b : a 不等于 b"
    else
    echo "$a == $b: a 等于 b"
    fi
    if [ $a -lt 100 -a $b -gt 15 ] # 与
    then
    echo "$a 小于 100 且 $b 大于 15 : 返回 true"
    else
    echo "$a 小于 100 且 $b 大于 15 : 返回 false"
    fi
    if [ $a -lt 100 -o $b -gt 100 ] # 或
    then
    echo "$a 小于 100 或 $b 大于 100 : 返回 true"
    else
    echo "$a 小于 100 或 $b 大于 100 : 返回 false"
    fi
    if [ $a -lt 5 -o $b -gt 100 ]
    then
    echo "$a 小于 5 或 $b 大于 100 : 返回 true"
    else
    echo "$a 小于 5 或 $b 大于 100 : 返回 false"
    fi

    # 逻辑运算符
    a=10
    b=20
    if [[ $a -lt 100 && $b -gt 100 ]] # 逻辑与
    then
    echo "返回 true"
    else
    echo "返回 false"
    fi
    if [[ $a -lt 100 || $b -gt 100 ]] # 逻辑或
    then
    echo "返回 true"
    else
    echo "返回 false"
    fi

    # 字符串运算符
    a="abc"
    b="efg"
    if [ $a = $b ] # 判断字符串是否相等
    then
    echo "$a = $b : a 等于 b"
    else
    echo "$a = $b: a 不等于 b"
    fi
    if [ $a != $b ] # 判断字符串是否不相等
    then
    echo "$a != $b : a 不等于 b"
    else
    echo "$a != $b: a 等于 b"
    fi
    if [ -z $a ] # 判断字符串长度是否为0
    then
    echo "-z $a : 字符串长度为 0"
    else
    echo "-z $a : 字符串长度不为 0"
    fi
    if [ -n "$a" ] # 判断字符串长度是否不为0
    then
    echo "-n $a : 字符串长度不为 0"
    else
    echo "-n $a : 字符串长度为 0"
    fi
    if [ $a ] # 判断字符串是否为空
    then
    echo "$a : 字符串不为空"
    else
    echo "$a : 字符串为空"
    fi
    echo ""

    # 文件测试运算符
    file="./test.sh"
    if [ -r $file ] # 判断文件是否可读
    then
    echo "文件可读"
    else
    echo "文件不可读"
    fi
    if [ -w $file ] # 判断文件是否可写
    then
    echo "文件可写"
    else
    echo "文件不可写"
    fi
    if [ -x $file ] # 判断文件是否可执行
    then
    echo "文件可执行"
    else
    echo "文件不可执行"
    fi
    if [ -f $file ] # 判断文件是普通文件还是特殊文件
    then
    echo "文件为普通文件"
    else
    echo "文件为特殊文件"
    fi
    if [ -d $file ] # 判断文件是不是一个目录
    then
    echo "文件是个目录"
    else
    echo "文件不是个目录"
    fi
    if [ -s $file ] # 判断文件是否为空
    then
    echo "文件不为空"
    else
    echo "文件为空"
    fi
    if [ -e $file ] # 判断文件是否存在
    then
    echo "文件存在"
    else
    echo "文件不存在"
    fi
  4. shell echo命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 显示普通字符串
    echo "\"显示转义字符\""

    # 显示变量
    read name # read 命令从标准输入中读取一行并赋值给name变量
    echo "$name is one kind of color"

    # 显示换行
    echo -e "hello\n" # -e开启转义
    echo "world"

    # 显示不换行
    echo -e "OK! \c" # \c不换行
    echo "It is a test"

    # 原样输出字符串
    echo '$name\"'

    # 显示命令执行结果
    echo `date`
  5. shell printf命令

    1
    2
    3
    4
    5
    6
    7
    echo "hello"
    printf "hello\n"

    printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
    printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
    printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543
    printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876
  6. test 命令

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
# test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试
# 数值测试
num1=100
num2=100
if test $[num1] -eq $[num2] # 代码中的 [] 执行基本的算数运算
then
echo '两个数相等!'
else
echo '两个数不相等!'
fi

# 字符串测试
num1="ru1noob"
num2="runoob"
if test $num1 = $num2
then
echo '两个字符串相等!'
else
echo '两个字符串不相等!'
fi

# 文件测试
if test -e ./test.sh
then
echo '文件已存在!'
else
echo '文件不存在!'
fi
  1. shell 流程控制

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    # 条件
    # 使用[...] 作为判断语句
    a=10
    b=20
    if [ $a == $b ]
    then
    echo "a 等于 b"
    elif [ $a -gt $b ]
    then
    echo "a 大于 b"
    elif [ $a -lt $b ]
    then
    echo "a 小于 b"
    else
    echo "没有符合的条件"
    fi
    # 使用 ((...)) 作为判断语句,大于和小于可以直接使用 > 和 <
    a=10
    b=20
    if (( $a == $b ))
    then
    echo "a 等于 b"
    elif (( $a > $b ))
    then
    echo "a 大于 b"
    elif (( $a < $b ))
    then
    echo "a 小于 b"
    else
    echo "没有符合的条件"
    fi

    # 循环
    # for循环
    for loop in 1 2 3 4 5
    do
    echo "The value is: $loop"
    done

    # while循环
    int=1
    while(( $int<=5 ))
    do
    echo $int
    let "int++" # Bash let 命令,它用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量
    done
    : '
    无限循环
    while :
    do
    command
    done

    while true
    do
    command
    done

    for (( ; ; ))
    '

    # until循环
    a=0
    until [ ! $a -lt 10 ]
    do
    echo $a
    a=`expr $a + 1`
    done

    # case多选择语句
    echo '输入 1 到 4 之间的数字:'
    echo '你输入的数字为:'
    read aNum
    case $aNum in
    1) echo '你选择了 1'
    ;;
    2) echo '你选择了 2'
    ;;
    3) echo '你选择了 3'
    ;;
    4) echo '你选择了 4'
    ;;
    *) echo '你没有输入 1 到 4 之间的数字'
    ;;
    esac

    # 跳出循环
    # break
    while :
    do
    echo -n "输入 1 到 5 之间的数字:"
    read aNum
    case $aNum in
    1|2|3|4|5) echo "你输入的数字为 $aNum!"
    ;;
    *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
    break
    ;;
    esac
    done

    # continue
    : '
    while :
    do
    echo -n "输入 1 到 5 之间的数字: "
    read aNum
    case $aNum in
    1|2|3|4|5) echo "你输入的数字为 $aNum!"
    ;;
    *) echo "你输入的数字不是 1 到 5 之间的!"
    continue
    echo "游戏结束"
    ;;
    esac
    done
    '
  2. shell 函数

    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
    32
    # 无返回值函数
    demoFun(){
    echo "这是我的第一个 shell 函数!"
    }
    echo "-----函数开始执行-----"
    demoFun
    echo "-----函数执行完毕-----"

    # 有返回值函数
    funWithReturn(){
    echo "这个函数会对输入的两个数字进行相加运算..."
    echo "输入第一个数字: "
    read aNum
    echo "输入第二个数字: "
    read anotherNum
    echo "两个数字分别为 $aNum 和 $anotherNum !"
    return $(($aNum+$anotherNum))
    }
    funWithReturn
    echo "输入的两个数字之和为 $? !"

    # 函数参数
    funWithParam(){
    echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    echo "第十个参数为 $10 !"
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
    }
    funWithParam 1 2 3 4 5 6 7 8 9 34 73
  3. 输入/输出重定向

    1
    echo 'print("hello")' > hello.py
  4. shell文件包含

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    : '
    shell 脚本中可以包含外部脚本

    语法格式:
    . filename # 注意点号(.)和文件名中间有一空格



    source filename
    '

构建工具CMake

  • 软件构建

    • 自动完成代码编译、链接、打包的整个过程
  • C和C++程序的构建工具-Cmake

  • Cmake的安装

  • gcc/clang编译工具安装(cmake不自带编译工具)

  • 示例代码 main.cpp

    1
    2
    3
    4
    5
    #include<iostream>
    int main(){
    std::cout << "hello,world" << std::endl;
    return 0;
    }
  • 创建CMakeLists.txt文件

    1
    2
    3
    4
    5
    cmake_minimum_required(VERSION 3.10)

    project(Example)

    add_executable(Example main.cpp)
  • 配置项目

    1
    cmake -S . -B build
  • 构建项目

    1
    cmake --build build

    构建完成后生成Example可执行文件,如下图所示:

  • 执行可执行文件

    1
    2
    3
    - cd ./build/
    ./Example
    # 输出hello,world

编译和链接

  • 通过一个代码示例理解编译与链接的过程

    1. 安装gcc

      1
      sudo apt-get install build-essential
    2. 编写示例代码

      main.c

      1
      2
      3
      4
      5
      6
      7
      8
      9
      #include<stdio.h>

      int add(int a, int b);

      int main(){
      printf("hello,world!\n");
      int result = add(5,5);
      return 0;
      }

      math.c

      1
      2
      3
      int add(int a, int b){
      return a + b;
      }
    3. 编译示例代码的两个文件main.c和math.c,得到对应的目标文件(Object File)main.o和math.o

      注:目标文件是二进制文件,文件格式是ELF(Executable and Linkable Format),ELF格式是linux下所有可执行文件的通用格式。

      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
      gcc -c main.c
      gcc -c math.c

      file main.o
      #main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), #not stripped

      readelf -h main.o
      ELF Header:
      Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
      Class: ELF64
      Data: 2's complement, little endian
      Version: 1 (current)
      OS/ABI: UNIX - System V
      ABI Version: 0
      Type: REL (Relocatable file)
      Machine: Advanced Micro Devices X86-64
      Version: 0x1
      Entry point address: 0x0
      Start of program headers: 0 (bytes into file)
      Start of section headers: 672 (bytes into file)
      Flags: 0x0
      Size of this header: 64 (bytes)
      Size of program headers: 0 (bytes)
      Number of program headers: 0
      Size of section headers: 64 (bytes)
      Number of section headers: 14
      Section header string table index: 13
      1
      2
      3
      readelf -S main.o
      # .text 代码区域
      # .data 数据区域

      objdump -s -d main.o

      -d:将代码段反汇编

      -s:将代码段反汇编的同时,将反汇编代码和源代码交替显示,编译时需要给出- g,即需要调试信息。

      右侧汇编指令中有两个call指令,既主函数中对printf和add的调用,从机器码可以看出跳转地址为0,需要在后续根据重定位表更新到printf和add的实际地址。

      objdump -r main.o

      查看两个函数调用的信息(地址偏移量、类型和值)

    4. 链接

      链接调用的函数机器码并组装为可执行文件main

    1
    gcc main.o math.o -o main
    1. 执行可执行文件main
    1
    2
    ./main
    # 输出hello,world
  • 通过makefile来进行编译链接步骤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    all: main

    main: main.o math.o
    gcc main.o math.o -o main

    main.o: main.c
    gcc -c main.c

    math.o: math.c
    gcc -c math.c

    clean:
    rm main main.o math.o
    1
    make main