1.结构体嵌套二级指针
当创建结构体变量后,如果其中的成员为指针类型的话,就要将此变量
开辟至堆空间,是因为在创建结构体后,程序在运行结束之后,对于结构体
变量所占的内存空间就已经分配好了,分配的大小为4字节(也就是一个指针
的)大小,如果不重新在堆空间中开辟内存的话,就会导致溢出,因此,一
般情况下,看到使用指针类型的数据的时候,应该在堆空间中存储。
同样的,当存在多级指针的时候,也就相应的要开辟多块存储空间,在
这个时候,记住先释放内层的堆空间,再释放外层的堆空间。
小技巧:浏览代码看malloc数和free数是否相等,如果相等的话一般都是
没问题的。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Teacher
{
char * name;
char * * student;
} ;
void test01 ( )
{
struct Teacher* * t1 = malloc ( sizeof ( struct Teacher* ) * 3 ) ;
for ( int i = 0 ; i < 3 ; i++ )
{
t1[ i] = malloc ( sizeof ( struct Teacher) ) ;
t1[ i] -> name = malloc ( sizeof ( char ) * 64 ) ;
sprintf ( t1[ i] -> name, "teacher_%d" , i + 1 ) ;
t1[ i] -> student = malloc ( sizeof ( char * ) * 5 ) ;
for ( int j = 0 ; j < 5 ; j++ )
{
t1[ i] -> student[ j] = malloc ( sizeof ( char ) * 64 ) ;
sprintf ( t1[ i] -> student[ j] , "teacher_%d_name_%d" , i + 1 , j + 1 ) ;
}
}
for ( int i = 0 ; i < 3 ; i++ )
{
printf ( "teacher_name = %s\n" , t1[ i] -> name) ;
for ( int j = 0 ; j < 5 ; j++ )
{
printf ( "studennt_name = %s\t" , t1[ i] -> student[ j] ) ;
}
printf ( "\n" ) ;
}
for ( int i = 0 ; i < 3 ; i++ )
{
if ( NULL != t1[ i] -> name)
{
free ( t1[ i] -> name) ;
t1[ i] -> name = NULL ;
}
for ( int j = 0 ; j < 5 ; j++ )
{
if ( t1[ i] -> student[ j] != NULL )
{
free ( t1[ i] -> student[ j] ) ;
t1[ i] -> student[ j] = NULL ;
}
}
if ( NULL != t1[ i] )
{
free ( t1[ i] ) ;
t1[ i] = NULL ;
}
}
if ( NULL != t1)
{
free ( t1) ;
t1 = NULL ;
}
}
int main ( )
{
test01 ( ) ;
return 0 ;
}
2.结构体成员偏移量
计算结构体中的成员偏移量的时候有两种办法:
1.通过计算不同数据类型所占的字节大小累计后得出目标成员的偏移量。
2.通过头文件<stddef.h>中的offsetof函数来进行计算
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
struct Teacher
{
char a;
int b;
} ;
void test01 ( )
{
struct Teacher t1;
struct Teacher * p = & t1;
printf ( "结构体中的b的偏移量:%d\n" , ( int ) & ( p-> b) - ( int ) p) ;
printf ( "结构体中的b的偏移量:%d\n" , offsetof ( struct Teacher, b) ) ;
}
void test02 ( )
{
struct Teacher t1 = { 'c' , 22 } ;
struct Teacher* p = & t1;
printf ( "结构体中的b的值:%d\n" , * ( int * ) ( ( char * ) p + offsetof ( struct Teacher, b) ) ) ;
printf ( "结构体中的b的值:%d\n" , * ( ( int * ) p + 1 ) ) ;
}
struct teacher2
{
char a;
int b;
struct Teacher c;
} ;
void test03 ( )
{
struct teacher2 t2= { 'a' , 10 , 'b' , 20 } ;
int offset1 = offsetof ( struct teacher2, c) ;
int offset2 = offsetof ( struct Teacher, b) ;
printf ( "属性C中的值为:%d\n" , * ( int * ) ( ( char * ) & t2 + offset1 + offset2) ) ;
printf ( "属性C中的值为:%d\n" , ( ( struct Teacher* ) ( ( char * ) & t2 + offset1) ) -> b) ;
}
int main ( )
{
test03 ( ) ;
return 0 ;
}
3.结构体字节对齐
在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中
所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。
从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是
事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这
就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这
就是内存对齐。
举一个很简单的例子,例如我们在内存中存储数字,假如是按照我们预期
的顺序存储的方式进行存储,存储一个char 和一个int型的数据,假设计算机
在读取的时候是4字节读取,那么就会先读到char类型的数据,然后读取int型
数据的前三位,这是一次读取,然后再读取下一块内容,读取int型数据的最
后一位数据,最后如果要得到这个数据,计算机还要进行拼接操作,将之前
所读取到的三个字节和第二次读取到的一个字节拼接起来,才可以得到完整
的数字,而通过字节对齐的方式进行存储,虽然在空间上浪费,但是在时间
上却大大提高了效率,因此,这是一种牺牲空间换取时间的存储方式,提高
了存取数据的效率。
对齐的原则为以下几点:
1.第一个属性开始,从零开始计算偏移量
2.第二个属性要放在,该属性大小与对齐模数比,二者较小的那个值
的整数倍当所有属性计算完毕后,整体做二次偏移,将上有计算的结果,
扩充到这个结构体中最大的数据类型的整数倍上
也就是说,其实对齐就是以结构体中最大的数据类型为基准,其余的
变量的存储都是以这个最大的数据类型的空间为准,能存储的下就存储,
存储不下就开辟一块新的最大的数据类型的空间进行存储。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(show)
typedef struct _STUDENT
{
int a;
char b;
double c;
float d;
} Student;
void test01 ( )
{
printf ( "student结构体的大小为:%d\n" , sizeof ( Student) ) ;
}
typedef struct _STUDENT2
{
char a;
Student b;
double c;
} Student2;
void test02 ( )
{
printf ( "student2结构体的大小为:%d\n" , sizeof ( Student2) ) ;
}
int main ( )
{
test01 ( ) ;
test02 ( ) ;
return 0 ;
}
4.文件
4.1 流的概念
流是一个动态的概念,可以将一个字节形象地比喻成一滴水,字节在
设备、文件和程序之间的传输就是流,类似于水在管道中的传输,可以看
出,流是对输入输出源的一种抽象,也是对传输信息的一种抽象。C语言
中,I/O操作可以简单地看作是从程序移进或移出字节,这种搬运的过程
便称为流(stream)。
4.2 文件指针
我们知道,文件是由操作系统管理的单元。当我们想操作一个文件的
时候,让操作系统帮我们打开文件,操作系统把我们指定要打开文件的信
息保存起来,并且返回给我们一个指针指向文件的信息。文件指针也可以
理解为代指打开的文件。这个指针的类型为FILE类型。该类型定义在stdio.h
头文件中。通过文件指针,我们就可以对文件进行各种操作。
在VS中,文件指针的结构体如下所示:
struct _iobuf {
char * _ptr;
int _cnt;
char * _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char * _tmpfname;
} ;
typedef struct _iobuf FILE;
4.3 文件缓冲区
在文件读取的过程中,其实都是通过文件缓冲区,来建立文件和操作
系统或者是操作系统和文件之间的互相传输内容,在开始读取的过程中,
都是先将读取的内容传输到流中,当流中内存占满时才进行下一步的输入或者
输出的操作这个过程既可以提高读取的速度,同时也可以提升我们硬盘的寿
命,因为每一次的读取过程都会减少硬盘的寿命,这样可以大大提升硬盘的
寿命。
文件操作完成后,如果程序没有结束,必须要用fclose()函数进行关闭,
这是因为对打开的文件进行写入时,若文件缓冲区的空间未被写入的内容填
满,这些内容不会写到打开的文件中。只有对打开的文件进行关闭操作时,
停留在文件缓冲区的内容才能写到该文件中去,从而使文件完整。再者一旦
关闭了文件,该文件对应的FILE结构将被释放,从而使关闭的文件得到保护
,因为这时对该文件的存取操作将不会进行。文件的关闭也意味着释放了该
文件的缓冲区。
4.4 文件的读写
4.4.1 文件读写的几种不同方式:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void test01 ( )
{
FILE * f_write = fopen ( "./test1.txt" , "w" ) ;
if ( f_write == NULL )
{
return ;
}
char buf[ ] = "hello world" ;
for ( int i = 0 ; i < strlen ( buf) ; i++ )
{
fputc ( buf[ i] , f_write) ;
}
fclose ( f_write) ;
FILE * f_read = fopen ( "./test1.txt" , "r" ) ;
if ( f_read == NULL )
{
return ;
}
char ch;
while ( ( ch = fgetc ( f_read) ) != EOF )
{
printf ( "%c" , ch) ;
}
fclose ( f_read) ;
}
void test02 ( )
{
FILE * f_write = fopen ( "./test2.txt" , "w+" ) ;
if ( f_write == NULL )
{
return ;
}
char * buf[ ] =
{
"锄禾日当午\n" ,
"汗滴禾下土\n" ,
"谁知盘中餐\n" ,
"粒粒皆辛苦\n"
} ;
for ( int i = 0 ; i < 4 ; i++ )
{
fputs ( buf[ i] , f_write) ;
}
fclose ( f_write) ;
FILE * f_read = fopen ( "./test2.txt" , "r" ) ;
if ( f_read == NULL )
{
return ;
}
while ( ! feof ( f_read) )
{
char temp[ 1024 ] = { 0 } ;
fgets ( temp, 1024 , f_read) ;
printf ( "%s" , temp) ;
}
fclose ( f_read) ;
}
struct Hero
{
char name[ 64 ] ;
int age;
} ;
void test03 ( )
{
FILE * f_wirte = fopen ( "./test3.txt" , "wb" ) ;
if ( f_wirte == NULL )
{
return ;
}
struct Hero heros[ ] =
{
{ "孙悟空" , 999 } ,
{ "亚瑟" , 20 } ,
{ "曹操" , 80 } ,
{ "鲁班" , 5 } ,
} ;
for ( int i = 0 ; i < 4 ; i++ )
{
fwrite ( & heros[ i] , sizeof ( struct Hero) , 1 , f_wirte) ;
}
fclose ( f_wirte) ;
FILE * f_read = fopen ( "./test3.txt" , "rb" ) ;
if ( f_read == NULL )
{
return ;
}
struct Hero temp[ 4 ] ;
fread ( & temp, sizeof ( struct Hero) , 4 , f_read) ;
for ( int i = 0 ; i < 4 ; i++ )
{
printf ( "姓名: %s 年龄:%d\n" , temp[ i] . name, temp[ i] . age) ;
}
fclose ( f_read) ;
}
void test04 ( )
{
FILE * f_write = fopen ( "./test4.txt" , "w" ) ;
if ( f_write == NULL )
{
return ;
}
fprintf ( f_write, "hello world %s" , "abcd" ) ;
fclose ( f_write) ;
FILE * f_read = fopen ( "./test4.txt" , "r" ) ;
if ( f_read == NULL )
{
return ;
}
char temp[ 1204 ] = { 0 } ;
while ( ! feof ( f_read ) )
{
fscanf ( f_read, "%s" , temp) ;
printf ( "%s\n" , temp) ;
}
fclose ( f_read) ;
}
void test05 ( )
{
FILE * f_wirte = fopen ( "./test5.txt" , "wb" ) ;
if ( f_wirte == NULL )
{
return ;
}
struct Hero heros[ ] =
{
{ "孙悟空" , 999 } ,
{ "亚瑟" , 20 } ,
{ "曹操" , 80 } ,
{ "鲁班" , 5 } ,
} ;
for ( int i = 0 ; i < 4 ; i++ )
{
fwrite ( & heros[ i] , sizeof ( struct Hero) , 1 , f_wirte) ;
}
fclose ( f_wirte) ;
FILE * f_read = fopen ( "./test51.txt" , "rb" ) ;
if ( f_read == NULL )
{
perror ( "文件加载失败" ) ;
return ;
}
struct Hero tempHero;
fseek ( f_read, - ( long ) sizeof ( struct Hero) * 2 , SEEK_END ) ;
rewind ( f_read) ;
fread ( & tempHero, sizeof ( struct Hero) , 1 , f_read) ;
printf ( " 姓名: %s , 年龄: %d\n" , tempHero. name, tempHero. age) ;
fclose ( f_read) ;
}
int main ( ) {
test05 ( ) ;
system ( "pause" ) ;
return EXIT_SUCCESS;
4.4.2 文件读写的注意事项
1.在进行单个字符(例如:getc、putc等函数的调用的时候)为什么不在
读取单个字符的时候使用feof来进行判断是否到文件末尾呢?
是因为fetc会使光标移动至下一个位置,因此会移动到EOF位置,并将
EOF打印出来后再进行判断。
解决办法如下所示的if代码
void test01 ( )
{
FILE* fp1 = fopen ( "./test01.txt" , "r+" ) ;
if ( NULL == fp1)
return ;
char ch;
while ( ! feof ( fp1) )
{
ch = fgetc ( fp1) ;
if ( ch == EOF )
break ;
printf ( "%c" , ch) ;
}
fclose ( fp1) ;
}
2.如果结构体中的有一个变量是指针类型,要将数据创建在堆区;也
就是说不要将这个指针变量存储在文件中。
4.5 配置文件
两种版本,一种是自己写的一种是网课上的,对比差距在代码易读性以及
对于结构体的熟练适应程度上有很大的差距 。
1.自己写的版本
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int getFilelines ( )
{
FILE* fp = fopen ( "test07.txt" , "r" ) ;
if ( fp == NULL )
return ;
int lines = 0 ;
char buffer[ 1024 ] = { 0 } ;
while ( ! feof ( fp) )
{
memset ( buffer, 0 , 1024 ) ;
fgets ( buffer, 1024 , fp) ;
if ( buffer[ 0 ] != '#' && strstr ( buffer, ":" ) != 0 )
lines++ ;
}
return lines;
}
void getFilevalue ( char * * p)
{
FILE* fp = fopen ( "test07.txt" , "r" ) ;
if ( fp == NULL )
return ;
char buffer[ 1024 ] = { 0 } ;
int index = 0 ;
while ( ! feof ( fp) )
{
memset ( buffer, 0 , 1024 ) ;
fgets ( buffer, 1024 , fp) ;
if ( buffer[ 0 ] != '#' && strstr ( buffer, ":" ) != 0 )
{
p[ index] = malloc ( sizeof ( char ) * 64 ) ;
memset ( p[ index] , 0 , 64 ) ;
for ( int i = 0 ; i < strlen ( buffer) ; i++ )
{
p[ index] [ i] = buffer[ i] ;
}
index++ ;
}
}
}
void findfileValue ( char * * p, int a)
{
printf ( "请输入您要查询的值:\n" ) ;
char inputArr[ 64 ] = { 0 } ;
scanf ( "%s" , inputArr) ;
int i = 0 ;
while ( i < a)
{
if ( strncmp ( p[ i] , inputArr, strlen ( inputArr) ) == 0 )
{
strtok ( p[ i] , ":" ) ;
printf ( "%s " , strtok ( NULL , ":" ) ) ;
break ;
}
++ i;
}
if ( i == a)
printf ( "无输入指定内容!" ) ;
}
void freeSpace ( char * * p, int a)
{
for ( int i = 0 ; i < a; i++ )
{
if ( p[ i] != NULL )
{
free ( p[ i] ) ;
p[ i] = NULL ;
}
}
free ( p) ;
p = NULL ;
}
void test01 ( )
{
int a = getFilelines ( ) ;
char * * p = malloc ( sizeof ( char * ) * a) ;
getFilevalue ( p) ;
findfileValue ( p, a) ;
freeSpace ( p, a) ;
}
int main ( )
{
test01 ( ) ;
return 0 ;
}
2.别人的版本
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct ConfigInfo
{
char key[ 64 ] ;
char value[ 64 ] ;
} ;
int getFileLine ( char * fileName ) ;
int isValidLine ( char * str) ;
void parseFile ( char * filePath, int lines, struct ConfigInfo * * configInfo) ;
char * getInfoByKey ( char * key, struct ConfigInfo * configInfo, int line) ;
void freeSpace ( struct ConfigInfo * configInfo) ;
struct Person
{
char a;
int b;
} ;
void test01 ( )
{
char * filePath = "./config.txt" ;
int line = getFileLine ( filePath) ;
printf ( "文件的有效行数为:%d\n" , line) ;
struct ConfigInfo * pArray = NULL ;
parseFile ( filePath, line, & pArray) ;
printf ( "heroId = %s\n" , getInfoByKey ( "heroId" , pArray, line) ) ;
printf ( "heroName = %s\n" , getInfoByKey ( "heroName" , pArray, line) ) ;
printf ( "heroAtk = %s\n" , getInfoByKey ( "heroAtk" , pArray, line) ) ;
printf ( "heroDef = %s\n" , getInfoByKey ( "heroDef" , pArray, line) ) ;
printf ( "heroInfo = %s\n" , getInfoByKey ( "heroInfo" , pArray, line) ) ;
freeSpace ( pArray) ;
pArray = NULL ;
int main ( ) {
test01 ( ) ;
system ( "pause" ) ;
return EXIT_SUCCESS;
}
int getFileLine ( char * fileName)
{
FILE * file = fopen ( fileName, "r" ) ;
if ( file == NULL )
{
return - 1 ;
}
char buf[ 1024 ] = { 0 } ;
int lines = 0 ;
while ( fgets ( buf, 1024 , file) != NULL )
{
if ( isValidLine ( buf) )
{
lines++ ;
}
}
fclose ( file) ;
return lines;
}
int isValidLine ( char * str)
{
if ( str[ 0 ] == ' ' || str[ 0 ] == '\0' || strchr ( str, ':' ) == NULL )
{
return 0 ;
}
return 1 ;
}
void parseFile ( char * filePath, int lines, struct ConfigInfo * * configInfo)
{
struct ConfigInfo * info = malloc ( sizeof ( struct ConfigInfo) * lines) ;
if ( info == NULL )
{
return ;
}
FILE * file = fopen ( filePath, "r" ) ;
char buf[ 1024 ] = { 0 } ;
int index = 0 ;
while ( fgets ( buf, 1024 , file ) != NULL )
{
if ( isValidLine ( buf) )
{
memset ( info[ index] . key, 0 , 64 ) ;
memset ( info[ index] . value, 0 , 64 ) ;
char * pos = strchr ( buf, ':' ) ;
strncpy ( info[ index] . key, buf, pos - buf) ;
strncpy ( info[ index] . value, pos + 1 , strlen ( pos + 1 ) - 1 ) ;
index++ ;
}
memset ( buf, 0 , 1024 ) ;
}
* configInfo = info;
}
char * getInfoByKey ( char * key, struct ConfigInfo * configInfo, int line)
{
for ( int i = 0 ; i < line; i++ )
{
if ( strcmp ( key, configInfo[ i] . key ) == 0 )
{
return configInfo[ i] . value;
}
}
return NULL ;
}
void freeSpace ( struct ConfigInfo * configInfo)
{
if ( configInfo != NULL )
{
free ( configInfo) ;
configInfo = NULL ;
}
}
4.6 简单的加密解密文件
简单的加密的过程就是将文件中的内容转化为单个字符,然后对单个字符
进行变化的过程,例如进行逻辑运算或者是加减运算,从而转换文件的具体
内容,达到加密的效果。
简单的解密的过程则于上述恰好相反,对于加密过程中的加减运算一
般是进行逆运算,对于逻辑运算则是通过左移或者是右移的方式进行还原,
下面自己写的一个简单的加密解密过程:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void passwdFile ( )
{
FILE* fp = fopen ( "passwdfile.txt" , "r" ) ;
FILE* tempfp = fopen ( "temppasswdfile.txt" , "w" ) ;
if ( fp == NULL || tempfp == NULL )
{
perror ( "文件打开失败!" ) ;
return ;
}
char buffer[ 1024 ] = { 0 } ;
while ( ! feof ( fp) )
{
memset ( buffer, 0 , 1024 ) ;
fgets ( buffer, 1024 , fp) ;
for ( int i = 0 ; i < strlen ( buffer) ; ++ i)
{
buffer[ i] + = 1 ;
buffer[ i] ^ = buffer[ i] ;
}
fputs ( buffer, tempfp) ;
}
fclose ( fp) ;
fclose ( tempfp) ;
remove ( "passwdfile.txt" ) ;
rename ( "temppasswdfile.txt" , "passwdfile.txt" ) ;
}
void openpasswdFile ( )
{
FILE* fp = fopen ( "passwdfile.txt" , "r" ) ;
FILE* tempfp = fopen ( "temppasswdfile.txt" , "w" ) ;
char buffer[ 1024 ] = { 0 } ;
while ( ! feof ( fp) )
{
memset ( buffer, 0 , 1024 ) ;
fgets ( buffer, 1024 , fp) ;
for ( int i = 0 ; i < strlen ( buffer) ; ++ i)
{
buffer[ i] - = 1 ;
}
fputs ( buffer, tempfp) ;
}
fclose ( fp) ;
fclose ( tempfp) ;
remove ( "passwdfile.txt" ) ;
rename ( "temppasswdfile.txt" , "passwdfile.txt" ) ;
}
void test01 ( )
{
passwdFile ( ) ;
openpasswdFile ( ) ;
}
int main ( )
{
test01 ( ) ;
return 0 ;
}