Size of()计算数组的大小
sizeof
可以直接用于计算整个数组占用的总字节数。要获取数组中的元素个数,可以使用:
sizeof(array) / sizeof(array[0])
这可以通过 sizeof
返回数组的总大小,然后除以单个元素的大小来得到元素的个数。
示例:
#include <stdio.h>
int main() {
int arr[10]; // 定义一个包含10个整数的数组
// 计算数组的总大小(字节数)
printf("Size of arr: %zu bytes\n", sizeof(arr)); // 输出数组占用的字节数
// 计算数组中元素的个数
printf("Number of elements in arr: %zu\n", sizeof(arr) / sizeof(arr[0])); // 输出数组元素的个数
return 0;
}
输出:
Size of arr: 40 bytes // 在32位系统上,int 通常占用4字节
Number of elements in arr: 10
计算结构体的大小
对于结构体,sizeof
返回的是结构体在内存中占用的总字节数。结构体的大小不仅取决于其成员的大小,还可能受到内存对齐的影响。
示例:
#include <stdio.h>
struct Example {
int a; // 占用4字节
double b; // 占用8字节
char c; // 占用1字节
};
int main() {
// 计算结构体的大小
printf("Size of struct Example: %zu bytes\n", sizeof(struct Example));
return 0;
}
输出:
Size of struct Example: 16 bytes // 由于内存对齐,结构体可能占用比其成员之和更多的内存
内存对齐对结构体大小的影响
内存对齐是 C 语言中的一个优化机制,它可能会导致结构体的实际大小大于其所有成员的大小之和。比如,编译器通常会在结构体中插入一些“填充字节”(padding bytes),使得结构体的成员在内存中的地址满足对齐要求。
示例:
#include <stdio.h>
struct AlignmentExample {
char c; // 占用1字节
int i; // 占用4字节
};
int main() {
printf("Size of struct AlignmentExample: %zu bytes\n", sizeof(struct AlignmentExample));
return 0;
}
输出:
Size of struct AlignmentExample: 8 bytes
- 虽然
char c
占用 1 字节,int i
占用 4 字节,但由于内存对齐,结构体的大小是 8 字节,而不是 5 字节。 - 这是因为编译器会在
char
和int
之间插入 3 个字节的填充,以确保int
变量按照 4 字节边界对齐。
数组和结构体的组合
如果你在结构体中定义了数组,你可以使用 sizeof
计算整个结构体的大小。
示例:
#include <stdio.h>
struct ArrayInStruct {
int arr[10]; // 定义一个包含10个整数的数组
char c; // 定义一个字符
};
int main() {
// 计算包含数组的结构体的大小
printf("Size of struct ArrayInStruct: %zu bytes\n", sizeof(struct ArrayInStruct));
return 0;
}
输出:
Size of struct ArrayInStruct: 44 bytes // 假设 int 占用 4 字节,char 占用 1 字节
- 数组
arr[10]
占用了 10 × 4 = 40 字节(假设int
占 4 字节)。 char c
占 1 字节。- 由于内存对齐规则,编译器可能会为结构体添加额外的字节,使结构体的总大小为 44 字节(而不是 41 字节)。
在 C 语言中,sizeof(struct tm)
和 sizeof(t)
可以是等价的:
sizeof(struct tm)
:直接获取struct tm
这个结构体类型在内存中占用的字节数。sizeof(t)
:获取变量t
在内存中占用的字节数。此时,t
必须是struct tm
类型的变量,才能返回结构体的大小。
示例:
#include <stdio.h>
#include <time.h>
int main() {
struct tm t; // 定义一个 struct tm 类型的变量
// 计算 struct tm 类型的大小
printf("Size of struct tm: %zu bytes\n", sizeof(struct tm));
// 计算变量 t 的大小
printf("Size of t: %zu bytes\n", sizeof(t));
return 0;
}
输出(在典型的系统上可能是):
Size of struct tm: 44 bytes
Size of t: 44 bytes
结论:
sizeof(struct tm)
是计算struct tm
结构体类型本身在内存中的大小。sizeof(t)
是计算t
变量的大小。因为t
是struct tm
类型的变量,sizeof(t)
返回的大小与sizeof(struct tm)
相同。
因此,在 t
是 struct tm
类型的变量时,sizeof(struct tm)
和 sizeof(t)
是等价的,都会返回相同的值。
strcmp
是 C 标准库中的一个字符串比较函数,用于比较两个字符串的大小。这个函数在 <string.h>
头文件中定义。其基本用法是比较两个以 null
结尾的字符串,返回一个整数值来表示两个字符串的相对大小。
strcmp
函数
int strcmp(const char *str1, const char *str2);
参数
str1
:第一个要比较的字符串。str2
:第二个要比较的字符串。
返回值
- 返回 0:如果
str1
和str2
内容相同(完全相等)。 - 返回负数:如果
str1
在字典顺序中小于str2
。 - 返回正数:如果
str1
在字典顺序中大于str2
。
比较规则
strcmp
函数是按字符逐个比较str1
和str2
的字符(基于 ASCII 值进行比较),直到找到第一个不同的字符或者遇到字符串结尾\0
。- 如果字符串长度不同,比较到短的字符串结尾处,较长字符串的字符将被视为更大。
示例:
#include <stdio.h>
#include <string.h>
int main() {
const char *str1 = "apple";
const char *str2 = "apple";
const char *str3 = "banana";
const char *str4 = "apricot";
// 比较相等的字符串
if (strcmp(str1, str2) == 0) {
printf("str1 and str2 are equal.\n");
}
// 比较不同的字符串
if (strcmp(str1, str3) < 0) {
printf("str1 is less than str3.\n");
}
// 比较不同的字符串
if (strcmp(str1, str4) > 0) {
printf("str1 is greater than str4.\n");
}
return 0;
}
输出:
str1 and str2 are equal.
str1 is less than str3.
str1 is greater than str4.
解释:
strcmp(str1, str2)
:str1
和str2
是相同的字符串,因此返回 0。strcmp(str1, str3)
:"apple"
小于"banana"
,因为"a"
的 ASCII 值小于"b"
,所以返回负数。strcmp(str1, str4)
:"apple"
大于"apricot"
,因为比较到第三个字符时,'p'
的 ASCII 值大于'r'
,所以返回正数。
常见的用法场景
- 字符串比较:
strcmp
最常用于两个字符串的比较,比如在排序算法中根据字母顺序排序,或者在查找特定的字符串时进行匹配。 - 判断字符串相等:在条件语句中,使用
strcmp(str1, str2) == 0
来判断两个字符串是否相等。 - 字典序排序:
strcmp
可用于排序函数,比如按字母顺序排列字符串数组。
#注意事项
- 大小写敏感:
strcmp
是大小写敏感的。例如,"Apple"
和"apple"
被认为是不相等的。如果需要忽略大小写的比较,可以使用strcasecmp
(POSIX 标准)。 - 传递的参数必须是以
'\0'
结尾的字符串:strcmp
假设每个字符串都是以null
终止的,因此必须确保传递的是正确的 C 字符串。
sscanf
是 C 语言标准库中的一个函数,用于从字符串中读取格式化的数据,并将其存储在指定的变量中。它的功能与 scanf
类似,但 sscanf
从字符串中读取数据,而不是从标准输入(键盘)读取。
strncmp
是 C 语言标准库中的一个字符串比较函数,用于比较两个字符串的前 n 个字符。与 strcmp
类似,strncmp
可以比较两个字符串,但它只比较指定数量的字符(即前 n 个字符),不会比较整个字符串。
strncmp
函数
int strncmp(const char *str1, const char *str2, size_t n);
参数解释
str1
:指向要比较的第一个字符串。str2
:指向要比较的第二个字符串。n
:指定比较的字符数,最多比较前n
个字符。
返回值
- 返回 0:如果
str1
和str2
的前n
个字符相等。 - 返回负值:如果
str1
在字典顺序中小于str2
的前n
个字符。 - 返回正值:如果
str1
在字典顺序中大于str2
的前n
个字符。
示例 1:比较两个字符串的前几个字符
#include <stdio.h>
#include <string.h>
int main() {
const char *str1 = "applepie";
const char *str2 = "applejuice";
// 比较前 5 个字符
if (strncmp(str1, str2, 5) == 0) {
printf("The first 5 characters are equal.\n");
} else {
printf("The first 5 characters are not equal.\n");
}
return 0;
}
输出:
The first 5 characters are equal.
解释:
strncmp(str1, str2, 5)
比较str1
和str2
的前 5 个字符,它们都是"apple"
,因此返回 0,表示它们的前 5 个字符相等。
示例 2:使用 strncmp
检查前缀
#include <stdio.h>
#include <string.h>
int main() {
const char *str1 = "banana";
const char *prefix = "ban";
// 比较字符串是否以 "ban" 开头
if (strncmp(str1, prefix, 3) == 0) {
printf("str1 starts with 'ban'.\n");
} else {
printf("str1 does not start with 'ban'.\n");
}
return 0;
}
输出:
str1 starts with 'ban'.
解释:
strncmp(str1, prefix, 3)
用于比较str1
的前 3 个字符是否等于"ban"
,因为str1
是"banana"
,前 3 个字符确实是"ban"
,所以返回 0,表示匹配。
常见用法场景
(1) 限制字符串比较的长度:
strncmp
可以用来只比较两个字符串的前几个字符,而不是整个字符串,尤其适用于处理部分匹配或前缀匹配。
if (strncmp(data, "CNBR", 4) == 0 || strncmp(data, "VNBR", 4) == 0)
{
printf("Valid parking type.\n");
}
在这个例子中,strncmp
用于比较字符串的前 4 个字符,检查停车类型是否是 "CNBR"
或 "VNBR"
。
(2) 防止越界:
strncmp
允许你只比较部分字符串,避免了当字符串很长时,逐字符比较整个字符串的效率问题。同时它可以防止在未指定长度的情况下发生越界访问。
(3) 前缀匹配:
strncmp
可以用来检查字符串是否以某个特定的前缀开头,如网址、文件路径等前缀匹配。
注意事项
-
大小写敏感:
strncmp
是区分大小写的。如果需要忽略大小写,可以使用strncasecmp
(POSIX 标准)。 -
比较的字符数:如果字符串的长度小于
n
,strncmp
只会比较到字符串的末尾('\0'
)为止,不会越界。 -
返回值的使用:
strncmp
返回的正值或负值取决于字符串在字典顺序中的先后顺序。通常用strncmp(str1, str2, n) == 0
来判断它们是否相等。
总结
strncmp
用于比较两个字符串的前n
个字符,常用于部分匹配或前缀比较。- 返回值与
strcmp
类似:相等返回 0,str1
小于str2
返回负值,str1
大于str2
返回正值。 - 可以避免比较整个字符串,提高效率,同时防止内存越界问题。
sscanf
函数
int sscanf(const char *str, const char *format, ...);
参数解释
str
:指向要解析的字符串。format
:格式化字符串,定义了要读取的数据格式(类似于printf
或scanf
的格式化符号)。...
:后面的参数是指向要存储解析结果的变量的指针。
返回值
- 成功时,
sscanf
返回成功读取并赋值的项数。 - 如果没有成功读取任何数据,返回
0
。 - 如果遇到错误,返回
EOF
。
示例 1:从字符串中读取整数和字符串
#include <stdio.h>
int main() {
const char *data = "12345 Hello World";
int number;
char str1[10], str2[10];
// 使用 sscanf 从字符串 data 中读取一个整数和两个字符串
sscanf(data, "%d %s %s", &number, str1, str2);
printf("Number: %d\n", number);
printf("String 1: %s\n", str1);
printf("String 2: %s\n", str2);
return 0;
}
输出:
Number: 12345
String 1: Hello
String 2: World
解释:
sscanf(data, "%d %s %s", &number, str1, str2)
从字符串data
中读取一个整数12345
,并将其存储在变量number
中;然后读取两个字符串"Hello"
和"World"
,分别存储在str1
和str2
中。
示例 2:从字符串中读取带格式的日期
#include <stdio.h>
int main() {
const char *date = "2023-09-30";
int year, month, day;
// 使用 sscanf 解析日期字符串
sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
printf("Year: %d, Month: %d, Day: %d\n", year, month, day);
return 0;
}
输出:
Year: 2023, Month: 9, Day: 30
解释:
sscanf(date, "%4d-%2d-%2d", &year, &month, &day)
:从字符串date
中提取出一个年份(4 位数)、一个月份(2 位数)和一个日期(2 位数),并将它们分别存储在year
、month
和day
中。
示例 3:从字符串中读取多个格式化的整数
#include <stdio.h>
int main() {
const char *time_str = "12:34:56";
int hours, minutes, seconds;
// 使用 sscanf 解析时间字符串
sscanf(time_str, "%2d:%2d:%2d", &hours, &minutes, &seconds);
printf("Hours: %d, Minutes: %d, Seconds: %d\n", hours, minutes, seconds);
return 0;
}
输出:
Hours: 12, Minutes: 34, Seconds: 56
解释:
sscanf(time_str, "%2d:%2d:%2d", &hours, &minutes, &seconds)
:从time_str
中提取小时、分钟和秒数,并分别存储在hours
、minutes
和seconds
变量中。
示例 4:处理字符串中的多个变量
#include <stdio.h>
int main() {
const char *data = "123 45.67 ABC";
int num;
float f;
char str[10];
// 解析整数、浮点数和字符串
sscanf(data, "%d %f %s", &num, &f, str);
printf("Number: %d, Float: %.2f, String: %s\n", num, f, str);
return 0;
}
输出:
Number: 123, Float: 45.67, String: ABC
解释:
sscanf(data, "%d %f %s", &num, &f, str)
:从字符串data
中解析出一个整数123
、一个浮点数45.67
和一个字符串"ABC"
,分别存储在num
、f
和str
中。
常见注意事项
-
字符串格式必须匹配:
sscanf
读取的格式必须与输入字符串严格匹配,否则会导致数据读取失败或不正确的行为。- 例如,如果你使用
%d-%d-%d
读取字符串"2023/09/30"
,会失败,因为/
与-
不匹配。
- 例如,如果你使用
-
读取的变量必须是指针:
sscanf
函数需要将数据写入传入的变量,因此这些变量必须通过指针传递(例如&number
,而不是number
)。 -
返回值:
sscanf
函数返回成功读取的字段数。检查返回值是确保正确读取数据的一个好办法。- 例如:如果期望从字符串中读取三个值,但
sscanf
返回的值不是 3,则表示数据读取不完整或有误。
- 例如:如果期望从字符串中读取三个值,但
将字符串转换为时间戳相关逻辑函数
首先,时间戳通过 convert_to_timestamp
函数将格式化的时间字符串(例如 YYYYMMDDHHMM
)转换为 time_t
类型的时间戳,以便后续计算。time_t
是 C 语言中用于表示时间点的标准类型。
相关代码:
time_t convert_to_timestamp(const char *time_str)
{
struct tm t;
memset(&t, 0, sizeof(struct tm));
sscanf(time_str, "%4d%2d%2d%2d%2d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min);
t.tm_year -= 1900; // 年份是从1900年开始计算的
t.tm_mon -= 1; // 月份是0-11的范围
return mktime(&t); // 将 tm 结构转换为 time_t 类型
}
作用:
- 解析格式化的时间字符串并存储为
struct tm
结构。 - 通过
mktime
函数将struct tm
转换为time_t
类型的时间戳。 - 时间戳表示车辆进入或离开停车场的时间点,后续计算中将使用这个时间戳进行时间差的计算。
计算停车时长
当车辆离开停车场时,停车时长是通过计算车辆离开时间的时间戳与进入时间的时间戳之间的差异来得出的。这是通过 calculate_parking_duration
函数实现的。
相关代码:
uint32_t calculate_parking_duration(time_t entry_time, time_t exit_time)
{
double duration_seconds = difftime(exit_time, entry_time);
uint32_t duration_hours = (uint32_t)((duration_seconds + 3599) / 3600); // 四舍五入
return duration_hours;
}
作用:
- 计算
entry_time
和exit_time
之间的时间差(秒数),并将其转换为小时数。 - 使用
difftime
函数计算秒数差异,再通过除以 3600 将秒数转换为小时。 - 这个停车时长(小时)用于后续的停车费用计算。
格式化时间
为了让时间更具可读性,代码中还提供了 format_time
函数,该函数将 time_t
类型的时间戳转换为易读的日期和时间格式,并输出给用户。
相关代码:
void format_time(time_t time, char *formatted_time)
{
struct tm *tm_info = localtime(&time); // 将 time_t 转换为本地时间的 struct tm
strftime(formatted_time, 100, "%Y年%m月%d日%H小时%M分钟", tm_info);
}
作用:
-
通过
localtime
函数将time_t
转换为本地时间的struct tm
结构。 -
通过
strftime
将其格式化为类似"2023年09月30日12小时30分钟"
的可读字符串。
利用指针获得数组索引:
- 计算要删除的记录的索引:
int index = record - vehicle_records;
record
是指向vehicle_records
数组中某个元素的指针。vehicle_records
是一个数组的起始地址。record - vehicle_records
是一种指针运算,用于计算record
在数组中的位置,实际上是计算record
和vehicle_records
之间的偏移量。- 例如,如果
record
指向vehicle_records
数组的第 2 个元素,那么index
就会等于1
(因为数组索引是从 0 开始的)。 - 这相当于确定数组中要删除的记录的索引
index
。
- 例如,如果
- 将数组中后面的元素向前移动:
for (int i = index; i < vehicle_count - 1; i++)
{
vehicle_records[i] = vehicle_records[i + 1];
}
- 这个
for
循环的作用是将vehicle_records
数组中,从index
位置开始的所有后续元素向前移动一位。 i = index
:从要删除的记录开始。i < vehicle_count - 1
:直到数组的最后一个有效记录。vehicle_count - 1
是当前最后一辆车的索引,因为vehicle_count
是车辆总数。vehicle_records[i] = vehicle_records[i + 1]
:将下一个元素vehicle_records[i + 1]
移动到当前索引i
,从而覆盖被删除的元素。- 这个过程实现了删除记录后,数组元素的紧凑排列。
- 减少车辆记录数量:
vehicle_count--;
vehicle_count--
:将车辆数量减 1,表示停车场中少了一辆车,数组中有效的车辆记录数减少。
总结:
- 这段代码的目的是在
vehicle_records
数组中删除一条记录,删除后通过将后续记录向前移动来保持数组的紧凑性。 - 删除操作的关键步骤是通过指针运算确定要删除的元素的索引,并通过循环将数组中该元素之后的每个元素向前移动一位,最后更新数组中车辆记录的总数量。
假设的例子:
如果 vehicle_records
数组中有 5 辆车的记录,数组元素如下:
索引 | 记录(车辆 ID) |
---|---|
0 | ABCD |
1 | EFGH |
2 | IJKL |
3 | MNOP |
4 | QRST |
假设我们要删除 EFGH
的记录(record
指向 vehicle_records[1]
),那么删除的步骤是:
index = record - vehicle_records
得到index = 1
。- 将数组中
index = 1
之后的所有元素向前移动:vehicle_records[1] = vehicle_records[2]
->EFGH
被IJKL
覆盖。vehicle_records[2] = vehicle_records[3]
->IJKL
被MNOP
覆盖。vehicle_records[3] = vehicle_records[4]
->MNOP
被QRST
覆盖。
- 数组最后一位 (
vehicle_records[4]
) 现在可能仍然保存着QRST
,但是由于vehicle_count
减少了 1,后续不会再访问到这条记录。