XSHELL 远程连接ssh客户端
    XFTP 与远程服务器传输文件

    虚拟机内存 2G
    NAT模式
    apt-get和pip太慢 可以在命令后面加
    -i http://pypi.douban.com/simple
    -i http://mirrors.aliyun.com/pypi/simple/
    -i https://pypi.mirrors.ustc.edu.cn/simple/
    -i http://pypi.douban.com/simple/
    -i https://pypi.tuna.tsinghua.edu.cn/simple/
    -i http://pypi.mirrors.ustc.edu.cn/simple/

    使用Xshell远程连接
    ubuntu 需要先安装openssh-server
    Xshell新建会话 主机填ubuntu的ip地址 ifconfig查看 enss inet:192.168.246.128
    VScode要安装的插件 Remote Development
    SSH Targets config 选第一个目录
    Host 主机名称
    HostName ubuntu ip 192.168.246.128
    User 远程登陆的用户名(ubuntu 用户名)
    通过connect就能在新的窗口连接远程 打开文件夹 输入一次密码后就能在vscode上远程给ubuntu新建文件夹或其他文件操作
    vscode配置免密登录 ssh-keygen -t rsa生成本机私钥(id_rsa)和公钥(id_rsa.pub)到用户目录下的wzl89/.ssh 我们需要将本机公钥发给Ubuntu
    ssh-copy-id 或 ubuntu内cd .ssh(没有就也ssh-keygen -t rsa) vim创建一个vim authorized_keys
    内容是本机id_rsa.pub右键复制到那 按i即可编辑 按ESC+shift+:+输wq回车
    无密码配置暂时没用 没配置好
    使用Xftp6传输文件
    名称 随便 主机为ubuntu的ip 用户名和密码不要忘记填
    左边为本机目录右边为linux目录 两边的文件可以互相拖动

    sudo apt install gcc g++ (版本>4.8.5 c++11) 查看版本 gcc/g++ -v/—version
    gcc用于编译c语言 g++用于编译c++
    touch test.c 创建一个空的文本叫做test.c 或者可以直接vscode远程连 直接新建和编写
    gcc test.c -o app 编译后会将test.c编译为lib文件app 我可以通过./app来执行他
    gcc test.c 什么都不加会生成a.out a为默认名称.out为可执行文件的默认后缀 在xshell内可执行文件在ls打出来后为绿色 ./a.out也能执行

    GCC工作流程
    1.
    源代码.h.c.cpp->预处理器->预处理后源代码(头文件展开 删除注释 宏替换).i文件可看->编译器->汇编代码.s文件->汇编器->目标代码.o(二进制指令)
    目标代码.o与启动代码,库代码,其他目标代码通过链接器进行链接变为可执行程序.out(linux).exe(Win)

    gcc 文件.c -E -o 文件.i -E为预处理指令 -o表示指定生成文件名 gcc会将文件进行预处理变为.i 预处理文件

    gcc 文件.i -S -o 文件.s -S为编译指令 -o表示指定生成文件名 gcc会将.i文件进行编译变为.s 汇编文件
    gcc 文件.c -S 生成的为文件.s 直接做了预编译和编译将.c变为.s

    gcc 文件.s -c -o 文件.o -c为汇编指令 -o表示指定生成文件名 gcc会将.s文件进行汇编变为.o 目标代码(二进制)文件

    gcc 文件1.o 文件2.o 文件3.o -o 文件.out
    //////gcc -o 文件.out 文件1.o 文件2.o 文件3.o
    -o为输出链接指令 gcc会将1,2,3的.o文件进行链接 最终生成可执行文件.out
    gcc 文件.c 直接生成 a.out 将整个流程全做了

    -I(大写i) 目录 指定include的包含文件的搜索目录
    -g在编译时生成调试信息(gdb调试),该程序可被调试器调用
    -w不生成任何警告信息 -Wall 生成所有的警告
    -On n为0-3编译器优化的四个级别 -O0表无优化 -O3优化级别最高 默认-O1
    -l(小写L) 在程序编译时指定使用的库 -L 指定编译时搜索库的路径
    -fPIC/-fpic 生成与位置无关的代码 -shared 生成共享目标文件,通常在建立共享库时
    -std 指定c版本-std=c99默认-std=GNU C
    -D在编译程序时指定一个宏(常用于指定 DEBUG) 文件内不定义DEBUG 而在编译时定义DEBUG来有选择性地编译(DEBUG版本还是REALEASE版本) 文件内#ifdef DEBUG … #endif
    gcc 文件.c -o 文件.out -D DEBUG 这样编译出来的文件是有DEBUG宏的会会输出…内的信息

    gcc也能编译c代码,后缀为.c给g++,g++是把他当作c++程序。后缀为.cpp两者都把他认为是c++程序(gcc如果编译.cpp文件则会定义__cplusplus宏即把代码按照c++规则编译
    )。 编译阶段g++会调用gcc对于c++代码gcc与g++是等价的,但是因为gcc无法自动和c++程序所使用的库链接(使用gcc -lstdc++ 让gcc用c++标准链接),所以通常用g++进行链接为了统一起见就都用g++了。

    2.
    库文件(被编译后的二进制.a .lib 但其实也是.cpp .h我们可以include然后调用库中函数)不能单独运行(cmake中的library cmake中的execute则是可以单独执行的代码)
    静态库和动态库(共享库)的区别 静态库在程序的链接阶段被复制到了程序中,动态库在链接阶段没有被复制到程序中而是在运行时由系统动态加载到内存中供程序调用

    静态库的制作
    命名规则
    linux:libxxx.a lib前缀固定 xxx为库的名字自定义 .a为后缀固定
    Win:libxxx.lib
    制作
    1 gcc获得.o文件
    2将.o文件打包 使用ar工具(archive)
    ar rcs libxxx.a xxx.o xxx2.o
    rcs解释r将文件插入备存文件中 c建立备存文件 s索引(给.o文件创建索引 方便使用的时候快速找到要用的.o)

    原clac计算包的目录为 我们想将他制作成lib
    ├── calc
    │ ├── add.c
    │ ├── div.c
    │ ├── head.h
    │ ├── main.c 测试代码
    │ ├── mult.c
    │ └── sub.c
    gcc -c add.c div.c mult.c sub.c (生成.o文件add.o div.o mult.o sub.o)
    ar rcs libcalc.a add.o div.o mult.o sub.o
    /////////////
    也可以整理代码.h .c main分开变为单独包 更规范
    ├── library
    ├── devel(暂时存放编译出来的.o文件)
    ├── include
    │ └── head.h
    ├── lib(暂时为空程序里用到的lib应该放在这里 程序里编译出来的lib暂时也放这)
    ├── main.c
    └── src
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c
    命令都是在library下执行
    gcc -I ./include -c ./src/add.c -o ./devel/add.o 指定头文件目录-I 指定被编译文件目录-c 指定输出文件目录-o 或cd src/ gcc -c add.c sub.c mult.c div.c -I ../include直接在src下生成所有.o
    ar rcs ./lib/libcalc.a ./devel/add.o ./devel/div.o ./devel/mult.o ./devel/sub.o (在library目录下)

    如果要使用库 库所用的头文件也要给别人(头文件函数声明 能告诉别人这个库内有什么函数)
    库编译好后 编译使用库的程序(main.c 测试程序)
    gcc -I ./include main.c -o app -l calc -L ./lib
    大写i 小写L (指定使用的库不用加前缀lib和后缀.a) -L指定库所在的目录
    3.
    动态库的制作和使用
    linux:libxxx.so lib前缀固定 xxx为库的名字自定义 .so为后缀固定 在linux下为可执行文件
    Win:libxxx.dll

    动态库的制作
    1gcc得到.o文件 得到和位置无关的代码 gcc -c -fpic(-fPIC) a.c b.c
    -fPIC/-fpic 生成与位置无关的代码 x86 cpu下-fPIC和-fpic相同
    2gcc得到动态库 gcc -shared a.o b.o -o libcalac.so -shared 生成共享目标文件

    原calc计算包的目录为 我们想将他制作成动态库
    ├── calc
    │ ├── add.c
    │ ├── div.c
    │ ├── head.h
    │ ├── main.c 测试代码
    │ ├── mult.c
    │ └── sub.c
    在calc目录下
    gcc -c -fpic add.c div.c mult.c sub.c (生成.o文件add.o div.o mult.o sub.o)
    gcc -shared *.o -o libcalc.so
    /////////
    也可以整理代码.h .c main分开变为单独包 更规范
    ├── library
    ├── devel(暂时存放编译出来的.o文件)
    ├── include
    │ └── head.h
    ├── lib(暂时为空程序里用到的lib应该放在这里 程序里编译出来的lib暂时也放这)
    ├── main.c
    └── src
    ├── add.c
    ├── div.c
    ├── mult.c
    └── sub.c

    gcc -I ./include -c -fpic ./src/add.c -o ./devel/add.o指定头文件目录-I 指定被编译文件目录-c 指定输出文件目录-o
    gcc -shared ./devel/add.o ./devel/div.o ./devel/mult.o ./devel/sub.o -o ./lib/libcalc.so (在library目录下)

    库编译好后 编译使用库的程序(main.c 测试程序)
    gcc -I ./include main.c -o app -l calc -L ./lib
    大写i 小写L (指定使用的库不用加前缀lib和后缀.a) -L指定库所在的目录
    4.
    直接使用编译好的main(以指定库)./app
    ./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory(找不到动态库的文件)

    为什么会出错呢 动态库的工作原理

    静态库 gcc 进行链接时 会把静态库中代码打包到可执行程序中 gcc -I ./include main.c -o app -l calc -L ./lib (calc为静态库时 可执行程序app内是含这个库的)

    动态库 gcc 进行链接时 动态库的代码打不会被包到可执行程序中 gcc -I ./include main.c -o app -l calc -L ./lib (calc为动态库时 可执行程序app内是不含这个库的)
    在./app程序启动之后使用了动态库的API时 动态库会被动态载入器动态加载到内存中 通过ldd(list dynamic dependencies)命令检查动态库依赖关系
    ldd app
    linux-vdso.so.1 (0x00007ffdf49fc000)
    libcalc.so => not found
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9006a58000)标准 c语言库
    /lib64/ld-linux-x86-64.so.2 (0x00007f900704b000) 用于加载动态库的动态库动态载入器
    5.
    如何定位共享库文件呢?
    当系统加载可执行代码时,能够知道所依赖的库的名字,但这不够,还需要知道库所在的绝对路径。此时就需要系统的动态加载器来获取该绝对路径。对于elf格式的可执行程序(现在的Linux操作系统的可执行程序基本上都是ELF格式的),是由ld-linux….so(ld-linux….so就是动态载入器)来完成的(已知所依赖的库的名字),它先后搜索elf文件的DT_RPATH段(进程的虚拟空间)->环境变量LD_LIBRARY_PATH->/etc/ld.so.cache文件列表->/lib/,/usr/lib目录找到库文件后将其载入内存。

    如何解决
    1
    修改LD_LIBRARY_PATH,终端内输入env可以看到键值对形式的所有环境变量
    配置
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wen/lesson04/library/lib
    $LD_LIBRARY_PATH先获取原先LD_LIBRARY_PATH的内容 后面再添加新加的的目录(绝对路径)
    可以通过echo $LD_LIBRARY_PATH来查看是否添加成功
    这种配置方法在这个终端关闭后 临时环境变量就失效了 第二次还得重新配置
    2用户级配置
    vim .bashrc 在最后一行(shift+g移到最后一行 输入o往下插入一行)
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wen/lesson04/library/lib
    Esc+:+wq回车保存并退出
    . .bashrc(或source .bashrc)使修改生效
    3系统级配置
    sudo vim /etc/profile
    在最后一行(shift+g移到最后一行 输入o往下插入一行)
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wen/lesson04/library/lib
    Esc+:+wq回车保存并退出
    source /etc/profile使修改生效
    4/etc/ld.so.cache
    sudo vim /etc/ld.so.conf
    在最后一行(shift+g移到最后一行 输入o往下插入一行)
    添加/home/wen/lesson04/library/lib 保存并退出
    sudo ldconfig
    3/lib/,/usr/lib
    将动态库文件放在这两个目录下 但不建议这么做怕重名

    改好后再执行./app就能正确执行了

    库小推荐编译为静态库,大编译为动态库
    静态库在编译主程序的链接时gcc -I ./include main.c -o app -l calc -L ./lib
    被打包到可执行程序中去了,加载速度快。发布程序时直接提供可执行程序就行(给app就行)不需要其他文件移植方便。缺点是消耗系统资源浪费内存(我的两个应用程序都用了同一个静态库[app1 = main1+libclac.a app2 = main2+libclac.a],当我执行这两个应用程序时内存中是有两份静态库代码的main1 libclac…main2 libclac 就是因为静态库在链接时被打包进应用程序中了)。更新,部署,发布麻烦,当库内某个函数改了就需要重新编译链接静态库,库的使用者需要重新编译app1,app2,….等所用其他用到这个库的应用程序

    动态库的优点,实现进程间资源共享(共享库)同时启动多个用到此库的可执行文件时,内存中只会有一份库。更新部署发布简单,库不会被打包到应用程序中(可执行文件还是知道这个库名的,编译链接时会将库名加入程序内),只要库内的函数名和参数不变,我们只需要替换动态库文件不需要重新编译可执行文件。可以控制何时加载动态库,只有使用到时才会加载。加载速度比静态库慢(稍慢),发布程序时,除了提供可执行文件外还需要提供依赖的动态库


    Makefile
    1.
    Makefile文件定义了一系列的规则来指定哪些文件需要先编译,那些文件需要后编译,那些文件需要重新编译等等,Makefile文件就像一个shell脚本一样也可以执行操作系统的命令。
    带来的好处就是自动化编译只需要一个make命令整个工程完全自动编译,提高软件开发的效率,make是一个命令工具 用于解释Makefile文件中指令的命令,大多数IDE都有这个命令。Delphi的make,vc的nmake,GUN的make。

    Makefile或makefile都ok 目标为最终要生成的文件(伪目标除外),依赖为生成目标所需要的文件或目标,命令:通过执行命令对依赖操作从而生成目标(命令前一定要有TAB缩进)
    定义一个规则
    目标…:依赖….
    命令(shell命令)
    命令(shell命令)

    其他规则一般都是为第一条规则服务
    Makefile文件只能叫Makefile或makefile
    app:sub.c add.c mult.c div.c main.c 目标:依赖
    gcc sub.cadd.c mult.c div.c main.c -o app
    命令
    sudo apt install make
    在用makefile的目录下 直接在终端执行make就行了

    2.
    定义一个规则
    目标…:依赖….
    命令(shell命令)
    命令(shell命令)

    Make makefile文件,在真正执行前会检查规则中的依赖是否存在,如果存在则执行命令,不存在则向下检查其他的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了则执行改规则中的命令。 检测更新,在执行规则中的命令时会比较目标和依赖文件的时间,如果依赖的时间比目标的时间晚则需要重新生成目标。如果依赖的时间比目标早,目标不需要更新,对应规则中的命令不需要被执行(高效)。

    依赖不存在则向下查找是否有命令是用来生成依赖的(sub.o add.o mult.o div.o main.o在这个目录下都不存(假设sub.c add.c mult.c div.c main.c以及.h都在这同一目录下))
    (其他规则一般都是为第一条规则服务 第一条规则一般为最终执行的命令,如果下面的规则与第一条规则一点关系都没有 比如最下面编译一个b.c 这条规则是不会被编译的 一切围绕第一条规则展开)
    app:sub.o add.o mult.o div.o main.o 目标:依赖
    gcc sub.o add.o mult.o div.o main.o -o app
    命令
    sub.o:sub.c
    gcc sub.c -c -o sub.o
    add.o:add.c
    gcc add.c -c -o add.o
    mult.o:mult.c
    gcc mult.c -c -o mult.o
    div.o:div.c
    gcc div.c -c -o div.o
    mian.o:mian.c
    gcc main.c -c -o mian.o
    b.o:b.c (与第一条规则无关make时并不会被执行)
    gcc b.c -c -o b.o




    如果编译完什么都不改,再编译一次(再make一次)会提示app已是最新
    (如果依赖修改时间时间比目标修改时间早,目标不需要更新)

    如果只是在main.c中加个回车(加个回车也算修改)再编译会显示
    gcc main.c -c -o mian.o
    gcc sub.o add.o mult.o div.o main.o -o app
    因为main.c被改了(main.c(依赖)的修改时间晚于main.o(目标)的修改时间)所以main.c要重新编译,main.o是作为app的依赖所以app也要重新编译生成(如果依赖的最新修改时间比目标的最新修改时间晚则需要重新生成目标)

    3.
    自定义变量 变量名=变量值 var=hello
    预定义变量
    AR:归档维护程序的名称 默认值为ar 制作静态库的归档工具
    CC:C编译器的名称 默认值为cc 其实就是gcc
    CXX:c++编译器的名称 默认值为g++

    获取某个内容 也叫自动变量 只能在命令中使用
    $@:目标的完整名称
    $<:第一个依赖文件的名称
    $^:所有的依赖文件
    获取变量值 $(变量名) 要写括号的!! $(var)
    app:main.c a.c b.c
    gcc -c main.c a.c b.c
    等同于
    app:main.c a.c b.c
    $(CC) -c $^ -o $@
    $(CC)=gcc $^=main.c a.c b.c $@=app

    makefile中#后内容为注释

    src=sub.o add.o mult.o div.o main.o
    target=app
    $(target):$(src)
    $(CC) $(src) -o $(target)
    或$(CC) $^ -o $@

    模式匹配
    原:
    app:sub.o add.o mult.o div.o main.o 目标:依赖
    gcc sub.o add.o mult.o div.o main.o -o app
    sub.o:sub.c
    gcc sub.c -c -o sub.o
    add.o:add.c
    gcc add.c -c -o add.o
    mult.o:mult.c
    gcc mult.c -c -o mult.o
    div.o:div.c
    gcc div.c -c -o div.o
    mian.o:mian.c
    gcc main.c -c -o mian.o

    %.o:%.c —- sub.o:sub.c add.o:add.c …形式是一样的
    %为通配符,匹配一个字符串 上面的两个%匹配的是同一个字符


    src=sub.o add.o mult.o div.o main.o
    target=app
    $(target):$(src)
    $(CC) $(src) -o $(target)
    %.o:%.c
    $(CC) -c $< -o $@ #只需要这两句 就能替换上面的这一长串
    $<第一个依赖文件的名称,即%所代表的字符
    向下检查其他的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了则执行改规则中的命令

    $(wildcard 函数参数)
    功能:获取指定目录下指定类型的文件列表 参数:指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录一般使用空格间隔 返回:得到的若干个文件的文件列表 文件名之间用空格间隔
    $(wildcard .c ./src/.c) 获取当前目录和当前目录(makefile文件所处位置)下src文件夹内所有.c文件的文件名
    返回格式 a.c b.c c.c d.c…

    $(patsubst pattern,replacement,text)
    功能:查找text中的单词(单词以空格,tab,回车,或换行分隔)是否符合模式pattern,如果匹配则以replacement替换
    pattern可以用%表示任意长度的字符串(%.c 有后缀.c的字符串作为模式 查找text中是否有,,,.c形式的字符串) 用\%来代表真实的%字符。
    返回:被替换过后的字符串
    $(patsubst %.c,%.o,x.c bar.c) #text:x.c bar.c 符合%.c模式 则替换后x.o bar.o

    src=$(wildcard .c ) #sub.c add.c mult.c div.c main.c 并非我们要的.o
    objs=$(patsubst %.c,%.o,$(src) ) #输出sub.o add.o mult.o div.o main.o
    target=app
    $(target):$(objs)
    $(CC) $(objs) -o $(target)
    %.o:%.c
    $(CC) -c $< -o $@
    clean: #clean规则与第一个规则完全无关make时并不会被执行
    rm $(objs) -f

    在命令行输入make clean将会自动删除生成的.o文件,假如我们先touch clean(生成叫clean的文件)再执行make clean会提示clean以是最新,并没有执行我们规定的clean规则
    clean: 是无依赖的 目标(我们touch后产生的clean)总是新于依赖 所以make clean并不会执行clean(因为目标比依赖新)会提示clean以是最新

    #将clean定义为伪目标即在执行make clean并不会生成clean的可执行文件也就不会产生上面的问题,不会生成文件也就不会跟我们touch产生的同名文件比对时间
    .PHONY:clean
    clean:
    rm $(objs) -f

    最终:
    src=$(wildcard
    .c ) #sub.c add.c mult.c div.c main.c 并非我们要的.o
    objs=$(patsubst %.c,%.o,$(src) ) #输出sub.o add.o mult.o div.o main.o
    target=app
    $(target):$(objs)
    $(CC) $(objs) -o $(target)
    %.o:%.c
    $(CC) -c $< -o $@
    .PHONY:clean
    clean:
    rm $(objs) -f

    GDB调试
    1.
    GDB是由GNU提供的调试工具同GCC配套组成了一套完整的开发环境,GDB是LINUX和许多类UNIX系统中的标准开发环境。一般来说,GDB主要帮助完成下面四个方面的功能:1启动程序,可以按照自定义的要求随心所欲地运行程序,可单步执行2可让被调试的程序在所指定的短点位置挺住(断点可以是条件表达式)3当程序被停住时可以检查此时程序中发生的事4可以改变程序,将一个BUG产生的影响修正从而测试其他BUG

    通常在为调试而编译时,我们会尽量关掉编译器的优化选项(‘-o’),并打开调试选项(‘-g’)将代码信息打包进可执行文件中。另外,’-Wall’在尽量不影响程序行为的前提下打开所有warning,也可以发现许多问题,避免一些不必要的BUG
    gcc -g -Wall program.c -o program
    -g选项的作用时在可执行文件中加入源代码信息,比如可执行文件中第几条机器指令对应源代码的第几行,并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证GDB能找到源文件!!!!
    GDB命令
    启动 gdb 可执行文件
    退出 quit或q 使用帮助 help help set
    gdb为shell命令 后面的quit help….都是gdb命令

    给程序设置参数/获取设置参数
    比如在执行main函数时可以传入 argv argc参数 ./test 10 20这种形式来传入
    set args 10 20
    show args


    vim中set nu 可以显示行号
    查看当前文件代码
    list或l (从默认位置显示)
    list或l 行号 (从指定的行显示)
    list或l 函数名 (从指定的函数显示)

    查看当非前文件代码
    list或l 文件名:行号
    list或l 文件名:函数名

    设置显示的list行数
    show list或listsize
    set list或listsize 行数

    接下来用lesson05中的test.c演示一下
    gcc test.c -o test -g 编译test 并指定-g用于GDB调试
    gdb test 开始调试test(可执行文件)

    g++ bubble.cpp main.cpp select.cpp -o main -g (编译c++文件)
    list bubble.cpp:1 查看当非前文件代码(一定要加:行号或函数名)



    2. 断点操作
    /表示两种写法都可以
    设置断点
    b/break 行号
    b/break 函数
    b/break 文件名:行号
    b/break 文件名:函数
    查看已经打了的断点信息
    i/info b/break i b
    删除断点
    d/del/delete 断点编号
    设置断点无效
    dis/disable 断点编号
    设置断点生效
    ena/enable断点编号
    设置条件断点(一般用在循环位置)
    b/break 10 if i==5 10为循环内语句的行号,i为循环变量

    gdb 文件名
    list 先看下程序的断点要打在第几行
    break 9 在当前文件的第九行打断点
    braek bubble.cpp:11 在这个cpp文件(gdb当前没有在查阅这个文件)的第九行打断点

    3.调试命令
    运行GDB程序
    start (程序停在第一行)
    run (遇到断点才停)
    继续运行直到遇到下个断点停
    c/continue
    向下执行一行代码(不会进入函数体 直接将这个函数执行完)
    n/next
    变量操作
    p/print 变量名(打印变量名)
    ptype 变量名(打印变量类型)
    向下单步调试(遇到函数进入函数体内)
    s/step
    finish(跳出函数体 将函数剩下的执行完) 函数内没有断点才能跳出
    自动变量操作
    display num (自动打印指定变量值 num 为变量名)
    i/info display 可以查看变量的编号
    undisplay 编号

    其他操作
    set var 变量名=变量值
    until (跳出循环 前提循环里没有断点 且当前这次循环体要被执行完(执行回到了判断是否继续循环的那个地方))

    被打上断点的那行不会被执行 程序会执行完断点的上一行并停住