C学习笔记 知识集锦(二)

时间:2023-11-10 10:11:44

 1. 数组和指针 2. 字符串赋值 3. memset&memcpy 4. 机器数和真值,原码,反码和补码 5. 文件指针和文件描述符 6. 内存泄露和内存损坏 7. 什么是不可移植的程序 8. 动态库文件和静态库文件 9. make的行为

10. 库函数调用和系统调用

  数组和指针

    数组:同类型的数的集合

      特点: 1. 数据类型一致 2. 大小固定 3. 内存空间连续 4. 数组名本质是指针常量,是该组的地址

      初始化: int a [10] = {'\0'} = 0; 也可以使用循环来赋空,也可以使用memset来赋空,全部赋为某个值的时候可以使用memset,当有不同值的时候不可以

        void *memset (void *s, int ch,size_t n);

        void *memcpy (void *dest, void *src,size_t n);

        一维数组的初始化,后面可以决定前面

          int a[5] = {1,2,3,4,5};  //合法

          int a[5] = {1,2,3,};    //合法

          int a[] = {1,2,3,4,5};    //合法,后面决定前面的大小

        二维数组的初始化,二维数组不可以缺省行,但可以缺省列,且二维数组名是行指针,即a+1是跳一行

          int a[2][3] = {1,2,3,4,5,6};    //合法,很标准的二维数组的赋值

          int a[2][3] = {1,2,3,4,5,};    //合法,后面一个默认为0

          int a[2][3] = {{1,2,3,}{4,5,6}};  //合法,每行三个

          int a[2][3] = {{1,2,}{3,4,5,6}};  //合法,第一行最后一个默认为0

          int a[2][3] = {1,2,3,4,5,6,7};  //不合法,赋值的个数多余数组的个数

          int a[][3] = {1,2,3,4,5,6};    //不合法,不可以缺省行的个数

          int a[2][] = {1,2,3,4,5,6};    //合法,可以缺省列的个数

      使用: int a[10] = 0;

          1. a表示数组名,是第一个元素的地址,也就是元素a[0]的地址,等价于&a,查找元素直接a[1]/a[2]

          2. a是地址常量,因为a一直指向的是a[0]的地址,所以出现a++,或者是a=a+2赋值的都是错误的

          3. a是一维数组名,所以它是列指针,也就是说a+1是跳一列//二维数组是跳一行

    指针:指针变量是存储其他变量地址的变量

      指针举例:char *p,*q;

          p = "xyz";  //p并不等价于字符串"xyz",而是指向一个由xyz+\0四个字符组成的起始元素的指针

          q=p;    //把p赋值给q,只是复制这个指针并不复制指针指向的字符串,p,q指向的是同一块内存区域,即q,p的取值是相同的

          q[1] = 'y';  // &是取地址运算符,该操作符返回对象所在的内存地址 指针不能等同数组

        int *p,a[5] = {1,3,5,7,9,};最好分开定义,因为标识符是靠右结合的

        *p++:地址会变而值不变; (*p)++:是数值会变而地址不变, *p++:先执行++,再*取值,++的优先级更高

        *p++ = 3;因为本身数值为1,但是地址加1,所以移到3那里

        (*p)++ = 2;先取值再加1

        二级指针:只存放一级指针的地址

          指针的赋值与比较:在C++中指针变量的赋值和比较是基于指针变量的值,也就是说它所存储的地址,这样依赖,如果两个指针指向的是同一个对象,那么它们就是相等的

          如果指向不同的对象,那么即使指针变量指向的对象本身是相等的,指针变量也是不等的,举例,如果lhs和rhs是指针变量(兼容的类型),那么lhs = rhs 是使lhs指向rhs指向

          的同一个对象,数组事实上是一个指向内存的地址,而不是基本数组类型,对数组使用 = 的结果是复制两个指针的值,而不是复制整个数组

          ey:int x =7;

          int *p = &x,**q = p;

          答:*p = 7;*q=p;**q=7;

          简言之:int x = 7; int *p = &x, int **q = p;

          p = &x,推导出*p = 7;

  字符串赋值

    把s指针中的字符串复制到t指针中的方法

    1. while ((*t=*s)!=null) {  //完整版本

      s++;

      t++;

      }

    2. while(*t++ = *s++)    //高级版本 ++权限比*高,先执行++再执行*

  memset&memcpy  

    memcpy函数的用法 void *memcpy(void *dest, void *src,size_t n);

    函数的功能是从源src所指向的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

    注意size_t是字节数,一般都是sizeof()形式

    memset函数的用法 void *memset(void *s, int ch, size_t n);

    函数的功能是将s所指向的某一块内存中的前n个字节的内容全部设置为ch指定的ASCII码值,第一个值为指定的内存地址

    块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作,其返回值为指向s的指针,注意内存的相关事情

  机器数和真值,原码,反码和补码

    机器数:一个数在计算机中的二进制表示形式,叫做这个数的机器数,机器数是带符号的,在计算机中用一个数的最高位存放符号,正数为0,负数为1,

      例如:十进制中的+3,计算机中的字长为8位,转换成二进制即是00000011,如果是-3,则是10000011

    真值:机器数对应的真正数值就是机器数的真值  //原码,反码,补码的基础概念和计算方法,只有有符号数才有这三种码

    原码:符号位加上真值的绝对值,即用第一位表示符号,其余位表示值

      举例:拿1作为int型的八位来计算,一般情况下,int对于16位编译器来说就是16位

        [+1]原=0000 0001; [-1]原= 1000 0001

    反码:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反

        [+1]原=0000 0001; [+1]反= 0000 0001

        [-1]原=1000 0001; [-1]反= 1111 1110

    补码:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各个位取反,最后加1(补码是在反码的基础上加1)

        [+1]原=0000 0001; [+1]补= 0000 0001

        [-1]原=1000 0001; [-1]补= 1111 1111

  文件指针&文件描述符

    文件指针:C语言中使用文件指针作为I/O的句柄,文件指针指向进程用户区中的一个被成为FILE结构的数据结构

      FILE结构包括一个缓冲区和一个文件描述符

    文件描述符:文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在windows系统上,文件描述符被称作文件句柄),在linux系统中打开文件就会获得

      文件描述符,它是个很小的正整数(一般在0-255),每个进程在PCB(Process Control Block)中保存着一份文件描述符表

      文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针

  内存泄露&内存损坏

    内存泄露:未释放不再使用的内存称为内存泄露

    内存损坏:释放或改写正在使用的内存称为内存损坏

  什么是不可移植的程序

    例如我们把int的值看做一个确定不变的已知值定义了一个变量,那么这种程序就是不可移植的,程序也应该避免这种依赖于实现环境的行为

    如果依赖于实现环境,那么在另一个环境下此int如果与之前定义变量不同,那么就会进行类型转换,就此会导致不可知行为,像qint16等类型即是为移植而生

  动态库文件与静态库文件

    动态库文件:动态库文件的扩展名是.so

    静态库文件的扩展名是.a(静态库文件很大,比动态库文件大的多)

      不论静态还是动态,都是由.o文件创建的,都以lib开头, 当静态库文件与动态库文件同名时gcc会优先使用动态库,在程序编译时会被链接到目标代码中,程序运行时

      不再需要该静态库文件,而动态库文件在程序编译时并不会链接到目标代码,而是在程序运行时才被载入,因此在程序运行时还需要动态库文件存在

  make的行为

    输入make进行编译时,make程序在当前目录寻找名为makefile的文件,该文件作为工程文件已经被建立,这个文件列出了源代码文件间的依赖关系,make程序观察文件的日期,

    如果一个依赖文件的日期比它所依赖的文件旧,make程序执行依赖关系之后列出的规则  //makefile文件中的所有注释都从"#"开始一直延续要本行的末尾

  库函数调用与系统调用

    库函数调用:在所有的ANSI C编译器版本中,C库函数是相同的;它调用函数库中的一个程序;与用户程序相联系;在用户地址空间执行;它的运行时间属于"用户时间"

      属于过程调用,开销较小;在C函数库libc中有大约300个程序

    系统调用:各个操作系统的系统调用是不同的;它调用系统内核的服务;是操作系统的一个进入点;在内核地址空间执行;它的运行时间属于"系统时间";需要在切换到内核

      上下文环境然后切换回来,开销较大;在UNIX中大约有90个系统调用

      函数调用速度:系统函数调用<库函数调用<普通函数调用

    编译预处理不是C语言的一部分,不占运行时间,不要加分号,C语言编译的程序称为源程序,它以ASCII码数值存放在文本文件中