C言语中的类型体系

结构体

// 声明一个结构体
struct Person{
  char *name;
  int age;
  char *id;
};
// 声明一个结构体变量
struct Person person={
  .name="hello",.age=10,.id="123"
};
// 声明一个结构体的指针
struct Person *p_person=&person;
PRINTFLINE("%d:",person.age);
// 结构体指针访问的 -> 操作符
PRINTFLINE("%s:",p_person->name);
// 每次都要struct Person 很麻烦 那直接界说一个 别名 就能够简化操作了
typedef struct Person Person;
PRINTFLINE("%lu:", sizeof(Person));

内存对齐

仍是前面的程序

咱们看下 这个person的结构体 究竟占用多少个byte, 打印出来是24个byte

有人就觉得奇怪了,为啥? 2个char 一共16个byte 一个int 4个byte 应该是 20个byte 才对啊,

多出来的4个byte 哪来的?

咱们看下 内存快照,看一下这个person实践的存储

Android JNI 编程 - C语言基础知识 (二)

简略处理下这个问题

#pragma pack(2)

再试一下即可

联合体

union Operator{
  int a;
  double d;
};
union Operator op={.a=4,.d=1.0};
PRINTFLINE("%d %f",op.a,op.d);

看下执行成果

这个联合体别的言语应该是没有的,有个特点是 他所占用的内存便是他内部字段最大的内存大小,

比如这儿占用的内存大小便是8,

此外 由于是同享8个byte 内存大小,假如d有值,那么会覆盖掉a的区域,从而a的值就不是你设置的值了

这个特点 以我浅薄的常识,java kotlin 是没有类似机制的

枚举

typedef enum FILE_IMAGE{
  PNG,JPEG,WEBP
}

枚举倒是和java 差异不大,本质上都是int值, 上述的比如中便是 0 1 2 3个值

下面这个比如便是 0 5 6

typedef enum FILE_IMAGE{
  PNG,JPEG=5,WEBP
}

判别字节序

假定内存中 有个4个字节 别离存了4个值 别离是 01 02 03 04, 在内存中 从左到右分布着 这4个字节

关于cpu来说 怎样读这4个字节就有意思了

0x04030201 这样便是小端序 (一般cpu都是小端读) 0x01020304 这样便是大端序 (一般是网络传输用大端序)

既然环境是不一样的,那么有时分 咱们需要来判别一下当时体系是大端仍是小端序

这个其实用union 来判别就很容易 。 咱们能够写个程序来验证一下

假定咱们要存一个值 是 0x100 内存中的分布 无非便是00 01 (小端) 或者是 01 00(大端)

根据union 来表明一下

bool isSmallEndian() {
  union {
    char c[2];
    short s;
  } value = {.s=0x100};
  return value.c[0] == 0;
}

C言语中的字符串

这个末节 个人觉得有个形象就能够了,需要用的时分 直接谷歌搜一下api即可,没必要花时间去记(字符串比较,字符串查找,字符串拆分,字符串的衔接,字符串的仿制

怎么判别一个字符是数字仍是字母

标准库都有现成的,有爱好的能够看下实现,这儿不多说了

#include <stdio.h>
#include <ioprint.h>
#include <ctype.h>
int main(){
  PRINTFLINE("%d:",isdigit('1'));
  return 0;
}

唯一要留意的是这儿面假如回来值是0 代表 不是,>0 就代表是, 可是不一定是1

办法挺多的:

Android JNI 编程 - C语言基础知识 (二)

包括一些转化类的函数:

Android JNI 编程 - C语言基础知识 (二)

字符串的转化

c言语中转化 主要是两种方法 atoX 简略场景用这个足够了 strtoX 更安全,功用更强大

stdlib.h 下:

Android JNI 编程 - C语言基础知识 (二)

字符串长度

strlen

Android JNI 编程 - C语言基础知识 (二)

字符串拆分

这儿有经验的老司机 必定能猜到 这个strtok 这个函数的写法 ,在多线程环境下 必定是不安全的

#include <stdio.h>
#include <stdlib.h>
#include <ioprint.h>
#include <ctype.h>
#include <string.h>
int main() {
  char string[] = "c,1972;c++,1983;java,1995;Rust,2010;Kotlin,2011";
  typedef struct {
    char *name;
    int year;
  } Language;
  const char *language_break = ";";
  const char *fieled_break = ",";
  int language_cap = 3;
  int language_size = 0;
  // 动态请求一块内存,由于不知道输入的字符串究竟有多长 天然也不知道这个结构体会有几个,索性先动态请求一块
  // 长度为3 size的 内存
  Language *languages = malloc(sizeof(Language) * language_cap);
  // 假如检索不到 则回来null 不然回来检索到的第一个字符串
  char *next = strtok(string, fieled_break);
  while (next) {
    Language language;
    language.name = next;
//    strtok函数的第一个参数是要切割的字符串,第二个参数是用于指定分隔符的字符串。在第一次调用strtok时,
//    咱们需要即将切割的字符串作为第一个参数传递给它。之后,咱们能够在后续调用中将第一个参数设置为NULL,
//    以表明继续运用上一次调用回来的状况来切割同一字符串
    next = strtok(NULL, language_break);
    if (next) {
      language.year = atoi(next);
      // 判别是否要扩大内存
      if (language_size + 1 >= language_cap) {
        language_cap *= 2;
        languages = realloc(languages, language_cap);
      }
      languages[language_size++] = language;
      next = strtok(NULL, fieled_break);
    }
  }
  PRINTFLINE("langeuages :%d",language_size);
  int i;
  for (i = 0; i < language_size; ++i) {
    PRINTFLINE("name=%s,year=%d",languages[i].name,languages[i].year);
  }
  free(languages);
  return 0;
}

常见的内存操作函数

mem最初的部分函数 str最初的都有

Android JNI 编程 - C语言基础知识 (二)

差异便是 mem最初的这几个函数 多了一个size函数, 原因很简略 mem不知道你要操作啥类型天然不知道 究竟要多少size了,而str清晰知道你是字符串 知道你最后是null结尾, mem最初的 并不知道这些

还记得前面一个章节说的 请求一个数组的时分 要做初始化嘛,现在memset更加便利做初始化了

int main() {
  char *mem = malloc(10);
  memset(mem,0,10);
  PRINT_INT_ARRAY(mem ,10);
  free(mem);
  return 0;
}

在 C 言语中,memmove() 和 memcpy() 都用于在内存中移动一段数据。它们的功用类似,但有一些差异。

memmove() 函数能够在堆叠的内存区域中移动数据,而 memcpy() 函数则不能。假如源和意图内存区域堆叠,而且需要在堆叠的内存区域中移动数据,就必须运用 memmove() 函数,不然可能会发生不行猜测的成果。

#include <stdio.h>
#include <stdlib.h>
#include <ioprint.h>
#include <ctype.h>
#include <string.h>
int main() {
  char src[] = "helloworld";
  char *dest = malloc(11);
  memset(dest, 0, 11);
  memcpy(dest, src, 11);
  puts(dest);
  memmove(dest + 3, dest + 1, 4);
  puts(dest);
  return 0;
}

宽字符串和窄字符串

总结一下: 假如你要处理的字符串是用数字+英文,那么窄字符串就能够,假如包括中文那就宽字符串

窄字符字符串运用的是 ASCII 或 ANSI 字符集,而且每个字符只占用一个字节(8 位)。它们一般运用 char 类型表明。

宽字符字符串运用的是 Unicode 字符集,而且每个字符占用两个字节(16 位)。它们一般运用 wchar_t 类型表明。

当需要处理一些特殊字符集(如中文、日文、韩文等)时,宽字符字符串一般更加便利和实用。

// 宽字符字符串的界说
wchar_t* wstr = L"Hello, world!";
// 窄字符字符串的界说
char* str = "Hello, world!";

文件的输入输出

clion中作业区的概念

Android JNI 编程 - C语言基础知识 (二)

随便翻开一个文件吧, 看看基本操作

#include <stdio.h>
int main() {
  FILE *file = fopen("CMakeLists.txt", "r");
  if (file) {
    puts("open file success");
    fclose(file);
  } else {
    perror("fopen txt");
  }
  return 0;
}

到这儿的时分 运转大概率是要报错的

Android JNI 编程 - C语言基础知识 (二)

由于你编译成功今后的可执行文件的途径是在这儿

Android JNI 编程 - C语言基础知识 (二)

而你fopen传递的是一个相对途径,所以必然会报错了,

简略的修正办法便是 修正一下clion的 作业区装备即可

Android JNI 编程 - C语言基础知识 (二)

让他默许在这个途径下作业即可

文件流的缓冲

#include <stdio.h>
int main() {
  FILE *file = fopen("CMakeLists.txt", "r");
  // 缓冲区的内存 生命周期要和文件流保持一致
  char buf[8192];
  if (file) {
    setvbuf(file, buf, _IOLBF, 8192);
    puts("open file success");
    fclose(file);
  } else {
    perror("fopen txt");
  }
  return 0;
}

Android JNI 编程 - C语言基础知识 (二)

这儿的3个宏稍微解释一下, FBF一般是读二进制文件用的,LBF 读文本文件用的, NBF 就不解释了

读取文件内容


#include <stdio.h>
int main() {
  FILE *file = fopen("CMakeLists.txt", "r");
  // 缓冲区的内存 生命周期要和文件流保持一致
  char buf[8192];
  if (file) {
    puts("open file success");
    setvbuf(file, buf, _IOLBF, 8192);
    // 读文件 留意getc回来的是int类型 绝对不是char
    int next_char = getc(file);
    while (next_char != EOF) {
      putchar(next_char);
      next_char = getc(file);
    }
    fclose(file);
  } else {
    perror("fopen txt");
  }
  return 0;
}

仿制文件的多种实现方法

第一种写法(功率低,可是能够仿制二进制文件):

//
// Created by 吴越 on 2023/2/28.
//
#include <stdio.h>
#include "ioprint.h"
#define COPY_ILLEGAL_ARGUMENTS (-1) // 参数过错
#define COPY_SRC_OPEN_ERROR (-2) // 源文件翻开失利
#define COPY_DEST_OPEN_ERRPR (-3) // 方针文件翻开失利
#define COPY_SRC_READ_ERROR (-4) // 源文件读取失利
#define COPY_DEST_WRITE_ERROR (-6) // 方针文件写入过错
#define COPY_UNKNOWN_ERROR (-5) // 不知道过错
#define COPY_SUCCESS (1) // 拷贝成功
int CopyFile(char const *src, char const *dest) {
  if (!src || !dest) {
    return COPY_ILLEGAL_ARGUMENTS;
  }
  FILE *src_file = fopen(src, "r");
  if (!src_file) {
    return COPY_SRC_OPEN_ERROR;
  }
  FILE *dest_file = fopen(dest, "w");
  if (!dest_file) {
    // 不要忘掉把源文件给关闭掉
    fclose(src_file);
    return COPY_DEST_OPEN_ERRPR;
  }
  int result;
  while (1) {
    // 一次读一个文字 其实功率很低
    int next = fgetc(src_file);
    if (next == EOF) {
      // 留意读取文件中 各种过错的判别
      if (ferror(src_file)) {
        result = COPY_SRC_READ_ERROR;
      } else if (feof(src_file)) {
        result = COPY_SUCCESS;
      } else {
        result = COPY_UNKNOWN_ERROR;
      }
      break;
    }
    if (fputc(next, dest_file) == EOF) {
      // 写入文件的过错 判别就很简略
      result = COPY_DEST_WRITE_ERROR;
      break;
    }
  }
  fclose(src_file);
  fclose(dest_file);
  return result;
}
int main() {
  int result = CopyFile("file/test.jpeg","file/test2.jpeg");
  int result2= CopyFile("file/1.txt","file/2.txt");
  PRINTFLINE("result: %d,result2 :%d",result,result2);
}

第二种写法,虽然加了缓存,读写功率更高,可是无法仿制二进制文件,只能仿制文本文件 由于fgets fputs 都是按行读写, 二进制文件 里边是没有行这个概念的,这儿一定要谨记哟

#define  BUFFER_SIZE 512
int CopyFile2(char const *src, char const *dest) {
  if (!src || !dest) {
    return COPY_ILLEGAL_ARGUMENTS;
  }
  FILE *src_file = fopen(src, "r");
  if (!src_file) {
    return COPY_SRC_OPEN_ERROR;
  }
  FILE *dest_file = fopen(dest, "w");
  if (!dest_file) {
    // 不要忘掉把源文件给关闭掉
    fclose(src_file);
    return COPY_DEST_OPEN_ERRPR;
  }
  int result = COPY_SUCCESS;
  char buffer[BUFFER_SIZE];
  char *next;
  while (1) {
    next = fgets(buffer, BUFFER_SIZE, src_file);
    if (!next) {
      if (ferror(src_file)) {
        result = COPY_SRC_READ_ERROR;
      } else if (feof(src_file)) {
        result = COPY_SUCCESS;
      } else {
        result = COPY_UNKNOWN_ERROR;
      }
      break;
    }
    if (fputs(next, dest_file) == EOF) {
      result = COPY_DEST_WRITE_ERROR;
      break;
    }
  }
  fclose(src_file);
  fclose(dest_file);
  return result;
}

终极版别(直接二进制读写)


int CopyFile3(char const *src, char const *dest) {
  if (!src || !dest) {
    return COPY_ILLEGAL_ARGUMENTS;
  }
  // windows体系一定要 加b后缀 linux和mac 无所谓
  FILE *src_file = fopen(src, "rb");
  if (!src_file) {
    return COPY_SRC_OPEN_ERROR;
  }
  FILE *dest_file = fopen(dest, "wb");
  if (!dest_file) {
    // 不要忘掉把源文件给关闭掉
    fclose(src_file);
    return COPY_DEST_OPEN_ERRPR;
  }
  int result = COPY_SUCCESS;
  char buffer[BUFFER_SIZE];
  while (1) {
    size_t bytes_read = fread(buffer, sizeof(buffer[0]), BUFFER_SIZE, src_file);
    // 这儿读多少字节 就要写多少字节,写的少了那就必定过错了
    if (fwrite(buffer, sizeof(buffer[0]), bytes_read, dest_file) < bytes_read) {
      result = COPY_DEST_WRITE_ERROR;
      break;
    }
    if (bytes_read < BUFFER_SIZE) {
      if (ferror(src_file)) {
        result = COPY_SRC_READ_ERROR;
      } else if (feof(src_file)) {
        result = COPY_SUCCESS;
      } else {
        result = COPY_UNKNOWN_ERROR;
      }
      break;
    }
  }
  fclose(src_file);
  fclose(dest_file);
  return result;
}

序列化与反序列化

先推荐装一个插件 ,比较容易看二进制

Android JNI 编程 - C语言基础知识 (二)

#include <stdio.h>
#include <stdlib.h>
#include <ioprint.h>
#include <string.h>
#include <wchar.h>
#define ERROR 0
#define OK 1
typedef struct {
  int visibility;
  int allow;
  int rate;
  int font_size;
} Settings;
int SaveSettings(Settings *settings, char *settings_file) {
  FILE *file = fopen(settings_file, "wb");
  if (file) {
    // 二进制写文件了, 
    fwrite(&settings->visibility, sizeof(settings->visibility), 1, file);
    fwrite(&settings->allow, sizeof(settings->allow), 1, file);
    fwrite(&settings->rate, sizeof(settings->rate), 1, file);
    fwrite(&settings->font_size, sizeof(settings->font_size), 1, file);
    fclose(file);
    PRINTFLINE("save success");
    return OK;
  } else {
    perror("fuck Failed to save settings");
    return ERROR;
  }
}
void LoadSettings(Settings *settings, char *settings_file) {
  FILE *file = fopen(settings_file, "r");
  if (file) {
    fread(&settings->visibility, sizeof(settings->visibility), 1, file);
    fread(&settings->allow, sizeof(settings->allow), 1, file);
    fread(&settings->rate, sizeof(settings->rate), 1, file);
    fread(&settings->font_size, sizeof(settings->font_size), 1, file);
    fclose(file);
  } else {
    perror("Failed to read settings");
    settings->visibility = -1;
    settings->allow = -1;
    settings->rate = -1;
    settings->font_size = -1;
  }
}
void PrintSettings(Settings *settings) {
  PRINTFLINE("visibility:%d , allow:%d, rate:%d,font_size:%d",
             settings->visibility, settings->allow, settings->rate, settings->font_size);
}
#define FILE_NAME "settings.bin"
int main() {
  Settings settings;
  LoadSettings(&settings, FILE_NAME);
  PrintSettings(&settings);
  settings.visibility = 5;
  settings.allow = 6;
  settings.rate = 7;
  settings.font_size = 8;
  SaveSettings(&settings, FILE_NAME);
  LoadSettings(&settings, FILE_NAME);
  PrintSettings(&settings);
  return 0;
}

然后看一下这个文件的16进制

Android JNI 编程 - C语言基础知识 (二)