【笔记】C 小程杂记

12 minute read
  • 八进制与十六进制

    • 0 开头表示八进制。0x 开头表示 16 进制。 07 vaild 08 invalid 0xf vaild 0xg invaild

    • %o 以八进制格式输出 / %x 以十六进制格式输出 (输出不带 00x

    • %#o 以八进制格式输出 / %#x 以十六进制格式输出 (输出带 00x

  • 设置输出宽度,可以在输出格式前加代表宽度的数字,比如 %14d,表示输出宽度为14个字符;设置输出宽度后,默认为右对齐,要设置左对齐,需要加-号,即 %-14d

  • scanf("%3d%2d",&i,&j); 输入 1234567, i = 123, j = 45.

  • 原码反码补码

    • 正数 - 原反补相同。

    • 负数 - 原码:符号位为1 / 反码:除了符号位,全部取反 / 补码:反码+1. e.g. char (-10): (10: 00001010) 原 10001010 反 11110101 补 11110110

    • 在计算机中,所有的整数使用补码计算与存储。

  • sizeof() 中的运算不会被进行。 int k = 3; sizeof(++k); k 的值仍是 3。

  • scanf 和 gets 的区别

    • 开头:scanf 忽略开头的所有空格。gets 不会忽略。

    • scanf 的结尾:scanf 遇到空格或者回车都会结束。同时,scanf 会将使其结束的空格(回车)留下来留给下一次读入。scanf 还会在最后加一个 \0

    • gets 的结尾:gets 遇到回车才会结束。同时,gets 会将使其结束的回车吃掉并将这个 \n 替换为 \0

(也就是说,两种读入方式均以 \0 结尾)

  • 字符数组、字符串常量、字符指针

    • 两种方式均可以表示字符串。

      1char str1[5] = "fuck"; /* 字符数组,并对其进行了初始化。*/
      2char *str2 = "shit"; /* 字符指针 str2 指向了一个字符串常量的首地址,
      3字符串常量在内存中的位置由系统自动安排。*/
      

      这两种声明方式下,str1str2 均是指向 首地址 f/s 的指针。

    • 字符 / 字符串的输出

      用 %s 可以输出从指针地址到第一个 '\0' 间的所有字符。

      用 %p 就是单纯地输出一个指针地址。

      用 %c 并解引用就是输出指针地址指向的那个字符。

       1#include <stdio.h>
       2int main()
       3{
       4    char *str = "fuckoff";
       5
       6    printf("%s\n", str); // fuckoff
       7    printf("%p\n", str); // 0040b044
       8    printf("%c\n", *str); // f
       9
      10    printf("%s\n", str + 1); // uckoff
      11    printf("%p\n", str + 1); // 0040b045
      12    printf("%c\n", *(str + 1)); // u
      13}
      
    • 只读性

      1char str1[5] = "fuck";
      2char *str2 = "shit";
      
      • 需要注意的是,和 int 数组一样,str1 将是一个只读的指针变量。我们不能 ++str1

        但是我们可以 ++str2 。之后 printf("%s\n", str2); 的结果为 hit。本质上是改变了 str2 的地址。

      • 不过,对于第一种声明方式,字符数组中的字符可以改变。比如我们可以 str1[2] = '\0',这之后 printf("%s\n", str1); 的结果为 fu

        对于第二种声明方式,由于是指向字符串常量,其中的字符无法改变。*(str2 + 2) = 'a';str2[2] = 'a'; 都是不合法的。但如果 str2 指向的是一个字符数组的话,就可以改变了。例如

         1#include <stdio.h>
         2int main() {
         3   char str[] = "fuckoff";
         4   char *p = "hello";
         5   p = str;
         6   printf("%s\n", str + 2); // ckoff
         7   *(p + 3) = 'z';
         8   printf("%s\n", p + 2); // czoff
         9   return 0;
        10}
        
  • 假设 p 是一个指针。p = NULL;p = 0; 都代表将 p 赋为空指针。

  • 动态内存申请

    • malloc 函数:void* malloc(unsigned size) 申请长度为 size 的内存空间并返回首地址。申请失败返回 NULL。

    • calloc 函数:void* calloc(unsigned n, unsigned size) 分配 n 个连续空间,每一存储空间的长度为 size,并且初始化值全部为 0。申请成功返回首地址。申请失败返回 NULL。

    • free 函数:void free(*p) 释放由动态存储分配函数(如上)申请到的整块内存空间。p 为指向要释放空间的首地址。

    • realloc 函数:void* ealloc(*p, unsigned size) 更改以前的内存分配。p 是之前由动态内存分配得到的指针,size 为现在需要的空间大小。分配失败保留原空间并返回 NULL。分配成功返回一片大小为 size 的区块,并保证该块的内容与原块的一致。注意,如果分配成功,原存储块的内容可能改变并不再允许使用。

    • 为什么 malloc, calloc, realloc 函数有返回值却是 void 类型?因为这是一种 通用指针,即它的返回值的类型是不确定的。当我们需要某种特定类型的返回值时,直接在前面用 (data type) 进行强制类型转换即可。(参见链表动态存储空间的申请)

  • Pointer More

    • 数组指针

      数组指针本质上是一个指向数组的指针变量。例如:

      1#include <stdio.h>
      2
      3int main() {
      4  int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
      5  int (*num)[4]; 
      6  num = &a[1];
      7  printf("%d\n", *(*num + 1)); // output 6.
      8  return 0;
      9}
      
    • 指针数组

      通过 char *str[5]; 可以声明一个含有 5 个元素的数组。每个元素的类型都是字符指针。

      指针数组的名字也是一个二级指针。比如上述例子中,str 本质上也是一个二级指针。

      此外,指针数组还可以进行类似数组的加法操作。下面的例子中,p[3][2] 等价于 *(p[3] + 2) 。(就像一维数组 *(a + 2) 等价于 a[2] 一样)

      1#include <stdio.h>
      2int main() {
      3      int i, a[12] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
      4      int *p[4];
      5      for (i = 0; i < 4; i++) p[i] = &a[i * 3];
      6      printf("%d\n", p[3][2]);
      7      return 0;
      8} // PTA 原题,输出 12。
      
    • 二级指针、二维数组与指针

      1int a = 10;
      2int *p = &a;
      3int **p = &p;
      

      上述代码中,p 为 a 的地址,pp 为 p 的地址。

      二维数组与指针:

      int a[3][4]; 可以看成由 a[0], a[1], a[2] 作为元素组成的一维数组(每个元素本身又是一个长度为 4 的一维数组)。

      a[0] 是一个一级指针,指向数组 a[0] 的首地址,即 &a[0][0] 。而 a 则为一个二级指针,指向首元素的地址 &a[0]

      推广地,a + 1 表示第一行的地址。*(a + 1) 表示第一行这个元素,也代表第一行首元素的地址。进而,**(a + 1) 表示第一行的首元素,亦即 a[1][0] 的值。

      *(*(a + i) + j) 等价于 a[i][j]

    • 指向函数的指针

      例如 int (*funcp)(int, int) 定义了一个函数指针 funcp。它可以指向任意有两个整型参数并且返回值为 int 类型的函数。

      假设函数 func(x, y) 已经定义,那么 funcp = func; 就可以将 func 的 入口地址 赋给 funcp(类比:数组名本质上也是指针),funcp 于是指向了函数 func(x, y)

      然后我们就可以这样调用函数:(*funcp)(1, 2);

    • 链表

      链表中每个节点由数据部分和下一个结点的地址构成。链表变量一般用指针 head 表示,用来存放链表首节点的地址。链表中的最后一个节点称为表尾,其下一个节点的地址为 NULL(注意表尾节点的数据部分仍是有效的)。

      • 链表的声明

        通常借用 结构体嵌套 的方式来定义一个链表。

        1struct node {
        2  int info1, info2, info3;
        3  struct node* next; // 结构的递归定义
        4};
        

        info 是这个节点的数据部分。next 则指向了下一个节点。

      • 链表是动态存储分配的数据结构

        比如,如果想要申请 node 结构的动态内存空间,需要使用以下语句。

        1struct node* p;
        2p = (struct node*)malloc(sizeof(struct node); // 需要强制转换
        
      • Basic Operations

        insert pn between p1 & p2

        1p1 -> next = pn;
        2pn -> next = p2;
        

        delete p2 (p1 points to p2)

        1p1 -> next = p2 -> next;
        2free(p2);
        
  • Struct

    • 关于 struct 的优先级问题:-> 大于 . 大于 * 大于 ++(--)

    • 关于 C 语言中 struct 的初始化:

      1#include <stdio.h>
      2struct A {
      3    int x, y;
      4};
      5int main() {
      6    struct A a[2] = { {1, 2}, {3, 4} };
      7    printf("%d %d %d %d\n", a[0].x, a[0].y, a[1].x, a[1].y);
      8    // Output: 1 2 3 4
      9}
      

      可以采取如上的初始化方式。

      但是需要注意,这种初始化方式必须在声明时使用。以下语句段是不合法的。

      1struct A a[2];
      2a[0] = {1, 2}; a[1] = {3, 4};
      

      但如果采用以下方式,又是可以的了。

      1struct A a[2];
      2a[0] = (struct A){1, 2}; a[1] = (struct A){3, 4};
      3// 这个相当于是把一个临时定义的 struct 赋到了 a[0] 和 a[1] 上。
      
  • Switch

    • case 后 只能为常量或常量表达式 。switch 支持的变量类型有 byte、short、int、char。

    • 循环体内的 switch 可以使用 continue。此时 continue 作用于循环体本身,即(执行 i++ 后)跳到下一次循环。