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实践的存储
简略处理下这个问题
#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
办法挺多的:
包括一些转化类的函数:
字符串的转化
c言语中转化 主要是两种方法 atoX 简略场景用这个足够了 strtoX 更安全,功用更强大
stdlib.h 下:
字符串长度
strlen
字符串拆分
这儿有经验的老司机 必定能猜到 这个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最初的都有
差异便是 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中作业区的概念
随便翻开一个文件吧, 看看基本操作
#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;
}
到这儿的时分 运转大概率是要报错的
由于你编译成功今后的可执行文件的途径是在这儿
而你fopen传递的是一个相对途径,所以必然会报错了,
简略的修正办法便是 修正一下clion的 作业区装备即可
让他默许在这个途径下作业即可
文件流的缓冲
#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;
}
这儿的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;
}
序列化与反序列化
先推荐装一个插件 ,比较容易看二进制
#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进制