HFC读书笔记

printf("%p, %p\n", &y, &x);  其中%p用来格式化地址,以16进制显示

数组变量可以当成指针使用, 但有不同的地方:

  1. sizeof(数组) 是数组的大小, 而用在指针上返回4或8(地址的长度)

  2. &数组变量 == 数组变量, 而&指针不同(是指针变量自己的地址)

  3. 数组变量并没有分配存储空间, 所以不能指向其它地方

scanf()的用法可参考printf()的用法, 需要设置长度, 例如字符数组, 需要减1, 留给\0, 避免缓冲区溢出, 对于字符串更好的是用fgets(),

例如:fgets(数组,sizeof(数组),stdin);     注意无须减1     配合sizeof运算符使用  备注 scanf -> scan formatted

char指针设置为字符串字面值, 确保使用const关键字: const char *s = "some string";  因为字面值是不可修改的

存储在只读区域,这样在编译时会报错

---------------------------------------------------------------------------------------------------------------------------------------------------:-)

熟悉使用string.h标准库, 处理字符串

---------------------------------------------------------------------------------------------------------------------------------------------------:-)

使用小工具: 

Ctrl + D 用于停止程序  但好像中止命令行程序的输出是Ctrl + C

程序结束时检查错误状态: UNIX用 echo $?    windows用 echo %ERRORLEVEL%

进程有标准输入和标准输出, 标准错误  stdin, stdout, stderr    它们都是流

当调用printf()时, 其实是调用了fprintf()函数的特殊  fprintf(stdout, "我喜欢乌龟!");

  可以用fscanf()来读取标准输入, 用法和scanf()很像

  可以用2>重定向标准错误

注意,运行程序时,包括重定向时一定注意程序路径与输入文件路径问题,否则会报错“没有那个文件或目录”

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

符号 | 表示管道(pipe), 它能连接一个进程的标准输出与另一个进程的标准输入

例如: (./bermuda | ./geo2json) < gpsdata.csv > output.json  数据流由管道从左到右流动,括号是必须的

除了标准输入, 标准输出, 标准错误, 还可以自建数据流. 使用fopen()函数

FILE *in_file = fopen("input.txt", "r");

w = 写; r = 读; a = 追加  (注意, 全是小写, 按照书上的例子, 不小心大写了, 运行时报"段错误")

创建数据流后, 可以使用fscanf()函数读数据, fprintf()函数写数据

使用完后需要关闭流: fclose(in_file);

如果出现错误, fopen()会返回0, 最好检查一个有没有错误发生! 相当于增加健壮性

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

int main(int argc, char *argv[])

可以向函数传递参数, 前面参数个数从程序名本身算起; 后面是char指针数组, 实际也就是字符串指针数组的意思

熟悉POSIX库中的unistd.h头文件中的函数getopt(), 用于处理命令行选项(即以"-"开头的命令行参数)

似乎运行示例碰到了问题,经过测试改进,发现去掉改变argc,argv的2行代码,后面打印材料循环初值改为optind

这样,就运行正常了 ,optind中的ind可能就是索引的意思,代表处理到第几个参数的索引了,可能是升级了库

vscode格式化代码快捷键(windows) shift + alt + f

命令行选项是负数的,比如 -d -4  中间用--隔开即可 -d -- -4

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

创建头文件只需要在目录中新建.h文件, 写上内容, 保存, 在其它程序的开头加上预处理命令#include "xxx.h"

  注意引号是相对路径查找头文件, 而尖括号是绝对路径查找头文件, 函数声明通常放在头文件中

  头文件的作用: 一是函数顺序问题; 另一个是共享代码, 到编译后期链接时有用

共享变量: 在头文件中声明, 在使用时加上extern, 例如: extern int passcode; 

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

gcc -c *.c 此命令会为目录内的所有源文件创建目标文件, 但不会链接成完整的可执行文件

gcc *.o -o launch 然后, 把目录内的目标文件链接起来

总结来说, 如果只修改了一个文件, 只需要重新编译这一个文件, 再重新链接成程序即可

但注意, gcc -c xxx.c -o xxx.o 就不会生成默认的目标文件, 其中后面一个xxx是可以指定的文件名

make是如何自动化代码的构建过程的:

  需要在目录中写出(约定的)makefile文件, 描述目标,依赖项和生成方法, 再用命令make launch

 其实原理非常简单, 只是要使用才能记住, 不然明天就忘得一干二净:-)

2024/3/25 今天写了一个makefile,使用make时却只执行了第一行:原因是因为最后的可执行文件生成语句放在了最后

  放在前面第一行就没事,好奇怪,没听说过!

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

为结构变量赋值相当于叫计算机复制数据

嵌套的结构, 在初始化数据时, 要使用多层花括号的写法

可以使用  typedef  为结构(/类型)创建别名, 就可以当成类型名使用, 其实只写类型名, 结构名省略, 编译器也没有意见:-)

 

  对了, 这样的话叫做匿名结构哦, 其实也没啥

在C语言中,参数按值传递给函数。要更新结构的值, 需要传递结构的指针,

而且可以使用简洁易阅读的语法更新字段: t->age 代表 (*t).age

注意, 上面是指针时用->, 非指针的情况下, 还是要用.号访问字段的!

联合: 声明后设置它的值常用方法 C89方式 quantity q = {4};  它会设置联合的第一个字段的值

  指定初始化器方法: quantity q = {.weight=1.5};

  "点"表示法: quantity q;  q.volume = 3.7; 这里使用了分行

c语言的位字段: typedef struct { unsigned int lowPassVcf:1; }  必须是unsigned int, 表示该字段只占用一位存储空间

  也即是说C语言的位是可以自定义的, 1位或多位空间占用,只要在冒号后面表达需要使用的位即可(2^位)

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

如果一个结构包含一个链向同种结构的链接, 那个这个结构就被称为递归结构.

另外递归结构要有名字, 因为里面有个相同类型的指针, 而C语言的语法不允许用typedef别名来声明它.

在C语言中, NULL的值实际为0, NULL实际专门用来把某个指针设为0

以前想不通,为什么结构的别名跟结构名可以相同,现在想如果加struct那就是结构名,否则是别名

申请存储器(堆空间)的函数叫malloc(), 是memory allocation (存储器分配的意思), 通常与sizeof运算符一起使用

  malloc(sizeof(island)); 当然需要头文件 #include <stdlib.h>

  malloc()返回通用指针(void*类型), island *p = malloc(sizeof(island));

  用完需要调用free()函数释放存储器: free(p);

  注意, 使用strdup()之后, 也要调用free(), 因为它通常调用了malloc(), 这要看标准库是如何实现的

堆之所以叫做堆: 计算机不会自动组织它, 它只是一大堆数据而已

 可能安装valgrind来检测是否存在内存泄漏, 加上选项, 并把程序传给valgrind

  valgrind --leak-check=full ./spies

当然, 可以在可执行文件中加入调试信息, 记录要编译代码的行号 -g 开关

  gcc -g spies.c -o spies

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

在C语言中, 函数名就是指向函数的指针, 有了函数指针, 就能把函数传给函数

但还是需要一个函数指针变量(写法): 返回类型 (*指针变量)(参数类型)

  例如: char** (*names_fn)(char*, int)  多个参数用逗号隔开

  函数指针使用中, 省略* 和&, 编译器也能识别它们, 而且更好读

  这样可以把相同签名的函数传递给函数使用

   names_fn = album_names;

  char** results = names_fn("Sacha Distel", 1972);

char** 并没有打错, 这是一个指什, 通常用来指向字符串数组

qsort()函数来自于标准库stdlib.h, 用于排序, 接收指向比较器函数的指针, 比较器函数可以比较两个值的大小

  比较器函数接收2个指针, 分别指向待排序数组中的两项

函数指针数组写法: 返回类型 (*指针变量)(参数类型) 例如: void (*replies[])(response);

  函数指针数组为调用一些相关函数提供了方便, 让代码易于管理和扩展, 从而可以伸缩.

  有了函数指针数组,就可以根据不同类型的数据运行不同的函数

参数可变的函数就称为可变参数函数, 需要包含stdarg.h, 函数写法固定如下

#include <stdarg.h>     //写可变参数函数需要
#include <stdio.h>

void print_ints(int args, ...)  //代表有可变参数
{
    va_list ap;                 //保存可变参数
    va_start(ap, args);         //从args参数开始后面都是
    int i;
    for (i = 0; i < args; i++)  //va_arg读取,包括读取参数的类型
        printf("argument: %i\n", va_arg(ap, int));
    va_end(ap);                 //代表需要销毁va_list
}

int main()
{
    print_ints(3, 79, 101, 32);
    return 0;
}

 

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

通常类UNIX操作系统会在 /usr/local/include 和 /usr/include 目录查找头文件, 有3种方式使用头文件

  1 只要头文件在标准目录中, 就可以使用尖括号包含它们: #include <stdio.h>

  2 也可以使用完整路径名: #include "/my_header_files/encrypt.h"

  3 也可以告诉编译器去哪里找头文件: gcc -I/my_header_files test_code.c

                    /my_object_files/encrypt.o ... -o test_code

用完整路径名共享.o目录文件, 跟指定头文件所在路径差不多写法, 以上方法适合共享几个文件的情况

那如何共享一大堆目标文件呢: 创建目标文件存档, 命令如下图

 可以使用nm命令查看存档中的内容:

 

只要创建目标文件存档, 就可以一次告诉编译器一批目标文件(应该就是说的压缩包吧:-)

用ar 命令创建存档: ar -rcs libhfsecurity.a encrypt.o checksum.o

  r表示存在就更新, c表示创建时不显示反馈, s表示要在开头建立索引, 后面是存档文件名, .a文件

  务必把存档命名为libXXX.a, 否则编译器找不到它们, 存档是静态库

nm命令可以查看存档中的内容, 它列出存档中保存的文件的名字: nm libhfsecurity.a

  另外 ar -t 文件名  会列出存档中的目标文件, 如下图

   ar -x libhfsecurity.a encrypt.o  该命令可以从存档中提取文件, 但必须是存档中有的才行!

当存档安装到标准目录(如/usr/local/lib): gcc test_code.c -lhfsecurity -o test_code

 

其它目录不是标准目录的情况: gcc test_code.c -L/my_lib -lhfsecurity -o test_code

由上面笔记及练习题指明: -I (大写字母i)用于指定头文件所在 -L用于指定存档文件所在

  -l (小写L)使用存档,  如果在当前目录, 后面跟点 .

----------------------------------------------------------------------------------------------------------------------------------------------------:-)

创建动态链接库

  创建目标文件  gcc -I. -fPIC -c hfcal.c -o hfcal.o  -fPIC意思是创建位置无关代码(事实上不加也没关系)

  -shard选项告诉gcc把.o目标文件转化为动态库, 一个平台一个叫法, 然后扩展名也不同, 如上图

  Linux叫共享目标文件, 里面还保存了库名, 所以编译后不能修改文件名, 除非重新编译

  不同操作系统寻找动态库的方式并不相同, 有点复杂, 在Linux中, 通常保存在/usr/lib或/usr/local/lib中

    不知道为何, 当动态库放在标准目录中, 还是不能运行程序, 试了好久, 网上查也没有解决:-(

    经过不懈努力, 终于找到了, 原因是我装的64位Linux(CentOS7), 相应需要把动态库拷贝到lib64目录中,Yes! Cool!

  而在Window中, 通常把.dll和可执行文件放在同一个目录

在Linux中, 需要设置LD_LIBRARY_PATH变量, 这样程序才能发现动态库

  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/libs  这里libs是共享目标文件所在目录,  上图是放在这个libs文件夹, 所以

  下面是编译和链接程序命令

  gcc -I. -c elliptical.c -o elliptical.o

  gcc elliptical.o -L. -lhfcal -o elliptical

  使用动态库的好处是, 如果修改(更新)了动态库,那么除了库文件外,源程序不需要重新编译

 ----------------------------------------------------------------------------------------------------------------------------------------------------:-)

操作系统内核管理进程, 存储器, 硬件, 而系统调用是程序用来与内核对话的函数

system()函数, 操作系统解释命令, 然后才运行

然后是exec()和它的两类变体,再说是fork(), 不过书本上的例子RSS什么的,没做成功,运行啥也没有,暂时这样吧先

----------------------------------------------------------------------------------------------------------------------------------------------------------:-)

由于书上讲的创建子进程和父子进程间使用管道通信的例子,实现碰到了问题,感觉例子过时,我反正没成功,无耐,

只好自己改写书上的例子加由实践理解了:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    char *w = getenv("EXTRA");
    if (!w)
        w = getenv("FOOD");
    if (!w)
        w = argv[argc - 1];
    char *c = getenv("EXTRA");
    if (!c)
        c = argv[argc - 1];
    printf("\n%s with %s\n", c, w);
    return 0;
}

coffee.c只在输出前加了换行符,其它没变,下面的test_coff.c改写如下:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

int main(int argc, char *argv[])
{
    char *ingredient = argv[1];
    char *my_env[] = {"FOOD=coffee", "FOOD=cream", NULL};
    // FILE *f = fopen("menu.txt", "w");
    // if (!f)
    //     error("Can't open menu.txt");
    int fd[2];                      //建立父子通信管道
    if (pipe(fd) == -1)
        error("Can't create the pipe");

    int i;
    for (i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == -1) 
            error("Can't fork process");

        if (!pid) {
            //重定位到文件的部分,允许在复制新进程时做各种处理
            // if (dup2(fileno(f), 1) == -1)
            //     error("Can't redirect Standard Output");
            close(fd[0]);       //关闭管道读
            dup2(fd[1], 1);     //用管道写标准输出

            if (execle("./coffee", "./coffee", ingredient,
                NULL, &my_env[i]) == -1) 
                error("Can't run process 0");
        }

        int pid_status;
        if (waitpid(pid, &pid_status, 0) == -1)
            error("等待子进程时发生了错误");

        dup2(fd[0], 0);     //用管道读标准输入
        close(fd[1]);       //关闭管道写

        char line[255];
        while (fgets(line, 255, stdin))
            printf("%s", line);

        printf("子进程id=%d ", pid);
        
    }
    printf("父进程id=%d", getpid());
    return 0;
}

 操作系统用信号控制进程  信号是O/S发出的消息

简单来说是利用信号映射表:信号(整型值)与处理函数的列表

可以用struct sigaction包装信号处理器函数,如果IDE提示不允许使用不完整类型,

可在代码开头加上#define _XOPEN_SOURCE   _XOPEN_SOURCE为了实现XPG:The X/Open Portability Guide 的功能

再用sigaction(signal_no, &new_action, &old_action)来向操作系统注册

用kill可以向进程发信号,而且一定可以杀死进程kill -KILL

进程可以用raise(SIGTERM)向自己发信号(通常接收低级别信号引发更高级别的信号--信号升级)

定时器alarm(120),120秒后可以发出SIGALRM信号,但注意与sleep()函数冲突

自定信号处理器后也可以重置SIG_DFL和忽略SIG_IGN

----------------------------------------------------------------------------------------------------------------------------------------------------------:-)

可使用最流行的POSIX线程库, 也就是pthread, 头文件是pthread.h

线程函数的返回类型必须是void*

创建并运行线程:

pthread_t t0;  if (pthread_create(&t0, NULL, does_not, NULL) == -1) error("无法创建");

等待线程结束, 得到结果:

void* result;  if (pthread_join(t0, &result) == -1) error("无法回收线程t0");

编译程序时, 需要链接pthread库(这里是动态链接):

> gcc argument.c -lpthread -o argument

#include <stdio.h>

int main()
{
    //对于普通类型变量
    int i = 5;
    printf("i的地址是%p, i的值是%d\n", &i, i);
    //对于指针变量
    int *m = &i;
    printf("指针变量m保存的内容是:%p, 指针m指向的值是%d\n", m, *m);

    printf("指针变量m的地址是%p\n", &m);
    //二级指针,指针变量的类型是在定义时确定的,并且不能在运行时改变

    printf("可以指向任意类型的指针,这个指针的地址是%p\n", (void**)&m);
    return 0;
}

再谈指针

 

posted @ 2023-06-18 15:07  Captain_Amazing  阅读(5)  评论(0编辑  收藏  举报