首页 > 其他分享 >c指针

c指针

时间:2023-08-01 09:12:03浏览次数:27  
标签:文件 int char str printf 指针

指针变量

数据类型 *指针变量名;

int *p;//定义了一个指针变量p, * 是用来修饰变量的,说明p是个指针变量,变量名是p

在定义指针变量的时候 * 代表修饰的意思修饰p是个指针变量。

关于指针的运算符:&取地址 *取值

p=&a;//把a的地址给p赋值 & 是取地址符

eg: p=&a;//p保存了a的地址,也可以说p指向了a

int num;

num=*p;

在调用的时候*代表取值的意思, *p就相当于p指向的变量,即a

故num=*p和num=a的效果是一样的。

所以num的值为a的值

void* p;通用指针//可以保存任何类型的地址编号

无论什么类型的指针变量,在32(64)位系统下,都是4(8)个字节。(编译器也要是64位)

指针只能存放对应类型的变量的地址编号。

*指针变量 就相当于指针指向的变量

指针为整型指针取4个字节,为double型则取8个字节

字符指针则取一个字节

指针++ 指向下个对应类型的数据

字符指针++,指向下个字符数据,指针存放的地址编号加1

整型指针++,指向下个整型数据,指针存放的地址编号加4

数组元素的指针引用

指针名加下标

eg:

int a[6];
int *p;
p=a;//等价于p=&a[0]
p[2]=100;//等价于a[2]=100

c语言规定:数组的名字就是数组的首地址,即第0个元素的地址,就是&a[0]的地址,是个常量。

注意:p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值。

eg:

p=&a[3];//正确
a=&a[3];//错误

通过指针变量运算加取值的方法来引用数组的元素

eg:

int a[5];
int *p;
p=a;
*(p+2)=100;//相当于a[2]=100,因为p=a为a的首地址p是第0个元素的地址,p+2是a[2]这个元素的地址。 对第二个元素的地址取值,即a[2]

通过数组名+取值的方法引用数组的元素

int a[5];
*(a+2)=100;//相当于a[2]=100;
注意:a+2是a[2]的地址,并没有给a赋值。

指针的运算

1.指针可以加一个整数,往下指几个它指向的变量,结果还是个地址。

int a[5];
int *p;
p=a;
p+2;//p是a[0]的地址,p+2是&a[2]
假如p保存的地址编号是2000的话,p+2代表的地址编号是2008(int 类型占4个字节sizeof(int))
char buf[5];
char *q;
q=buf;
q+2//相当于&buf[2]
假如q中存放的地址编号是2000的话,q+2代表的地址编号是2002(char 类型占1个字节sizeof(char))
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;

int value1 = p[2];
int value2 = *(p + 2);

printf("%d\n", value1); // 输出 30
printf("%d\n", value2); // 输出 30

 

2.两个相同类型指针可以比较大小

两个相同类型的指针指向同一个数组的元素的时候,比较大小才有意义。指向前面元素的指针 小于 指向后面元素的指针

int a[10];
int *p,*q;
p=&a[1];
q=&a[6];
if(p>q)
   printf("p大");
else if(p<q)
   printf("q大");
else
   printf("都大");
   

   

3.两个相同类型的指针可以做减法

必须是两个相同类型的指针指向同一个数组的元素的时候,做减法才有意义。做减法的结果是,两个指针指向的中间有多少个元素。

 int a[10];
   int *p, *q;
   p = &a[3];
   q = &a[6];
   if (p > q)
       printf("p大%d",p-q);
   else if (p < q)
       printf("q大%d",q-p);
   else
       printf("都大");

4.两个相同类型的指针可以相互赋值

只有相同类型的指针才可以相互赋值(void*类型的除外)

int *p;
int *q;
int a;
p=&a;//p保存a的地址,p指向了变量a
q=p;//用p给q赋值,q也保存了a的地址,指向a
//注意:类型不相同的指针想要相互赋值,必须进行强制类型转换

指针数组

在 C 语言中,指针数组是一个包含多个指针元素的数组。每个指针元素可以指向不同类型的数据或相同类型的数据。

要声明和使用指针数组,可以按照以下步骤进行操作:

  1. 声明指针数组:在声明时,使用指针声明符 来指示数组元素是指针类型。*

    int* ptrArray[5]; // 声明一个包含 5 个整数指针的指针数组
  2. 初始化指针数组:可以逐个初始化每个指针元素,也可以使用循环进行批量初始化。

    int a = 10, b = 20, c = 30;
    int* ptrArray[3] = { &a, &b, &c }; // 初始化一个包含 3 个整数指针的指针数组
  3. 使用指针数组:通过索引访问指针数组中的各个指针元素,并将其用于操作对应的数据。

    int* ptr = ptrArray[0]; // 获取第一个指针元素
    int value = *ptr; // 获取第一个指针元素所指向的整数数据
    printf("%d\n", value); // 输出 10

指针数组常用于需要存储多个指针的情况,例如在动态分配内存、处理字符串数组、实现多级指针等方面。

请注意,在使用指针数组时,确保指针元素指向的内存是有效的,并避免访问已释放的内存或悬空指针,以避免出现未定义行为。

指针的指针

指针的指针是指在 C 语言中,可以通过一个指向指针的指针变量来操作指针本身的概念。它允许我们通过间接引用的方式修改指针变量所指向的内存地址。

在 C 语言中,通过使用两个星号()来声明指向指针的指针变量。下面是一个示例:**

int main() {
   int value = 10;
   int* ptr = &value;     // 指向整数的指针

   int** ptr_ptr = &ptr;  // 指向指针的指针

   printf("Value: %d\n", **ptr_ptr);  // 间接引用获取值

   return 0;
}

字符串和指针

字符串的概念:

字符串就是以'\0'结尾的若干字符的集合:例如"hello world".

字符串的地址,是第一个字符的地址。如:字符串"hello world" 的地址,就是字符串中字符'h'的地址。

定义一个字符串指针变量保存字符串的地址,比如:char *s="hello world"

这个字符不是存放在s指针变量中的,s只是存放字符'h'的地址编号。"hello world"存放在文字常量区。

字符串的可修改性:

字符串内容是否可以修改,取决于字符串存放在哪里。

1.存放在数组中的字符串的内容是可以修改的

char str[100]="I love C!"
str[0]='y';//可以修改
注:数组没有用const修饰

2.存放在文字常量区里的

内容是不可以修改的

char* str="I love C!"
*str = 'y';//错误,I存放在文字常量区,不可更改
注:
   str指向文字常量区时,它指向的内存的内容不可被修改。
   str是指针变量可以指向别的地方,即可以给str重新赋值,让它指向别的地方。
   //eg:
   char *s="Y love C1";
   char *str="I love C!";
   printf("%s \n",str);
   str=s;
   printf("%s",str);

3.堆区的内容是可以被修改的

char *str = (char)malloc(10);

strcpy(str,"I love C!");

*str = 'Y';//可以,堆区内容是可修改的

注:
   str指向堆区的时候,str指向的内存内容是可以被修改的
   str是指针变量,也可以指向别的地方。即可以给str重新赋值,让它指向别的地方
注意:str指针指向的内存能不能被修改,要看str指向哪里。

str指向文字常量区时。内存内容不可修改。

str指向数组(非const修饰)、堆区时,它指向内存的内容是可以修改的

初始化:

1.字符数组初始化:
char buf_aver[20]="hello world";

2.指针指向文字常量区,初始化:

char buf_point="hello world";

3.指针指向堆区,堆区存放字符串不能初始化,只能先给指针赋值,让指针指向堆区,再使用strcpy、scanf等方法把字符串拷贝到堆区。

char *buf_heap;

buf_heap=(char *)malloc(15);

strcpy(buf_heap,"hello world");

scanf("%s",buf_heap);

使用时赋值:

1.字符数组:使用scanf,strcpy等。

char buf[20]="hello world";
buf = "hell kitty";//错误,字符数组名是个常量,不能用等号给常量赋值。
在 C 语言中,字符数组名是一个常量指针,指向字符数组的首地址,因此不能直接使用赋值运算符来更改字符数组的值。

2.指针指向文字常量区

char *buf_point="hello world";
buf_point="hello kitty";//正确,buf_point指向另一个字符串
strcpy(buf_point,"hello kitty");//错误,buf_point指向的是文字常量区,内容只读。当指针指向文字常量区时,不能通过指针修改文字常量区的内容。

3.指针指向堆区,堆区存放字符串

char *buf_heap;

buf_heap=(char *)malloc(15);

strcpy(buf_heap,"hello world");

scanf("%s",buf_heap);

数组指针

数组名是数组的首地址,是第0个元素的地址,是个常量,数组名+1指向下个元素。

二维数组a中,a+1指向下一个一维数组,即下一行。

  int a[3][5];
   printf("a=%p\n",a);
   printf("a+1=%p\n",a+1);
//输出:
a=000000000061FDE0
a+1=000000000061FDF4
   F4-E0=14HX=20=sizeof(int)*5

数组指针本身是个指针,指向一个数组,加1跳一个数组,即指向下个数组。

数组指针的定义方法:

指向的数组的类型 (*指针变量名)[指向数组元素的个数]

int (*p)[5];//定义了一个数组指针变量p,p指向的是整型的有5个元素的数组。p+1往下指5个整型,跳过一个有5个整型元素的数组。
int a[3][5];
   int (*p)[5];
   p=a;
   printf("a=%p\n",p);
   printf("a+1=%p\n",p+1);
//一般一维数组指针配合二维数组用,一维数组指针的个数必须等于二维数组的列数
void fun(int (*p)[5], int x, int y);
int main()
{
   int a[3][5] = {
      {1, 2, 3, 4, 5},
      {6, 7, 8, 9, 0},
      {11, 12, 13, 14, 15}};
   printf("a[1][2]=%d\n", a[1][2]);
   fun(a, 3, 5);
   printf("a[1][2]=%d\n", a[1][2]);
   return 0;
}

void fun(int (*p)[5], int x, int y)
{

   p[1][2] = 100;
}
//二维数组传参要用一维数组指针接收并且一维数组指针的个数必须等于二维数组的列数
二维数组指针,加1后指向下个二维数组

int(*p)[4] [5]

配合三维数组来用,三维数组中由若干个4行5列二维数组构成

int a[3] [4] [5]

注意:

容易混淆的概念:

1.指针数组:是个数组,由若干个相同类型的指针构成的集合。

int *p[10];

数组p由10个int *类型的指针变量构成p[0]~p[9]

2.数组指针:是个指针,指向一个数组,加1跳一个数组。

int(*p)[10];

p是个数组指针,p+1指向下个数组,跳10个整型。

3.指针的指针:

int **p;//p是指针的指针又称二级指针

int *q;

p=&q;

数组名取地址:变成 数组指针

一维数组名取地址,变成一维数组指针,即加1跳一个一维数组。

int a[10];

a+1 跳一个整型元素,是a[1]的地址

a和a+1相差一个元素,4个字节。

&a就变成了一个一维数组指针,是int(*p)[10]类型的。
a是int (*)类型的指针,是a[0]的地址
(&a)+1 和 &a相差一个数组即10个元素即40个字节。
a和&a 打印出的地址值是相同的,因为它们都指向了数组 a 的起始位置。但是它们的类型是不同的,a 的类型是int* ,而&a的类型是int(*)[10] 。

所以,总结起来:

a 是指向一维数组第一个元素的指针。
&a 是指向整个一维数组的指针。

数组名和指针变量的区别

int a[5];
int *p;
p=a;
//相同点:
a是数组的名字,是a[0]的地址,p=a即p保存了a[0]的地址,即a和p都指向a[0],所以在引用数组元素的时候,a和p等价。
a[0],*(a+2),p[2],*(p+2)都是对数组中a中a[2]元素的引用。
//不同点:    
a是常量p是变量
可以用等号'='给p赋值,但不能给a赋值
对a取地址和对p取地址结果不同
因为a是数组的名字,所以对a取地址结果为数组指针。
p是个指针变量,所以对p取地址&p结果为指针的指针。

数组指针取*

数组指针取*,并不是取值的意思,而是指针的类型发生变化:

一维数组指针取*,结果为它指向的一维数组第0个元素的地址,它们还是指向同一个地方。

二维数组指针取*,结果为一维数组指针,它们还是指向同一个地方。

三维数组指针取*,结果为二维数组指针,它们还是指向同一个地方。

多维以此类推

 int a[3][5];
   int (*p)[5];
   p = a;
   printf("a=%p\n", a);
   printf("*a=%p\n", *a);
   printf("*a+1=%p\n", *(a + 1));
这段代码展示了在C语言中使用指针访问二维数组中的元素。下面是每行代码的作用:

int a[3][5];:声明一个包含3行5列整数的二维数组。
int (*p)[5];:声明一个指向包含5个整数的数组的指针。
p = a;:将指针p赋值为数组a的第一个元素的地址,等价于&a[0]。
printf("a=%p\n", a);:打印数组a的第一个元素的地址。
printf("*a=%p\n", *a);:打印a的第一行中的第一个元素的地址,等价于&a[0][0]。
printf("*a+1=%p\n", *(a + 1));:打印a的第二行中的第一个元素的地址,等价于&a[1][0]。这是通过将指针*a(指向a的第一行的第一个元素)加1来实现的。
需要注意的是,当对一个指向二维数组的指针使用解引用运算符*时,得到的是指向数组的第一行第一个元素的指针。这就是为什么*a等价于&a[0][0]。当将一个整数添加到数组的指针上时,指针会向前移动该整数个数组元素。因此,*(a + 1)等价于&a[1][0]。
printf("*a+1=%p\n", *a + 1);
printf("*a+1=%p\n", *a + 1);:打印a的第一行中的第二个元素的地址,等价于&a[0][1]。这是通过将指向a的第一行的第一个元素的指针解引用并加1来实现的。
需要注意的是,当将一个整数添加到指向数组的指针上时,指针会向前移动该整数个数组元素。因此,*a + 1等价于&a[0][1]。

指针和函数的关系

指针作为函数的参数

就是给函数传一个地址

eg:

int num=0;
scanf("%d",&num);

函数传参

传数值:

void swap(int x, int y)//x,y是形参
{
   int temp;
   temp = x;
   x = y;
   y = temp;
   printf("x=%d,y=%d\n", x, y);
}
int main()
{
   int a = 10;
   int b = 20;
   swap(a, b);//a,b是实参
   printf("a=%d,b=%d\n", a, b);
   return 0;
}
输出:
x=20,y=10
a=10,b=20
结论:
   给被调函数传数值,只能改变被调函数形参的值,不能改变主调函数实参的值。
   

传地址:

void swap(int *x, int *y)
{
   int temp;
   temp = *x;
   *x = *y;
   *y = temp;
   printf("x=%d,y=%d\n", *x, *y);
}
int main()
{
   int a = 10;
   int b = 20;
   swap(&a, &b);
   printf("a=%d,b=%d\n", a, b);
   return 0;
}
结果:
   x=20,y=10
a=20,b=10
结论:
   调用函数的时候传变量的地址,在被调函数中通过*地址来改变主调函数中的变量的值。
void swap(int *x, int *y)
{
   int *temp;
   temp = x;
   x = y;
   y = temp;
   printf("x=%d,y=%d\n", *x, *y);
}
int main()
{
   int a = 10;
   int b = 20;
   swap(&a, &b);
   printf("a=%d,b=%d\n", a, b);
   return 0;
}
结果:
   x=20,y=10
a=10,b=20
总结:
   要想改变主调函数中变量的值,必须传变量的地址,而且还得通过*地址去赋值。
void fun(char **q)
{
   *q = "hello kitty";
   printf("q=%s\n", *q);
}
int main()
{
   char *p = "hello world";
   printf("p=%s\n", p);
   fun(&p);
   printf("p=%s\n", p);
   return 0;
}
/*
p=hello world
q=hello kitty
p=hello kitty*/

给函数传数组

给函数传数组的时候,没法一下将数组的内容作为整体传进去。

只能传数组名进去,数组名就是数组的首地址,即只能把数组的地址传进去。

传一维数组:

//void fun(int p[])
void fun(int *p)
{
 
   printf("p[2]=%d\n", *(p+2));
   *(p+3)=100;
}
int main()
{
   int a[10]={1,2,3,4,5,6,7,8,9,0};
   fun(a);
    printf("a[3]=%d\n", *(a+3));
     printf("a[3]=%d\n", a[3]);
   return 0;
}
void fun(int p[]):此声明等效于void fun(int *p) 因为数组参数会自动转换为指向其第一个元素的指针。

传二维数组:

// void fun(int p[][4])
void fun(int (*p)[4])
{

   printf("p[1][2]=%d\n", p[1][2]);
   p[0][1] = 100;
}
int main()
{
   int a[3][4] = {
      {1, 2, 3, 4},
      {5, 6, 7, 8},
      {9, 10, 11, 12}};
   fun(a);
   printf("a[0][1]=%d\n", a[0][1]);
   return 0;
}
void fun(int p[][4]):此声明等效于void fun(int (*p)[4]) 因为数组的第一维被忽略,而第二维被指定为指向 4 个整数的数组的指针。

传指针数组:

// void fun(char *q[],int x)
void fun(char **q, int x)
{

   for (int i = 0; i < x; i++)
  {
       printf("p[%d]=%s\n", i, q[i]);
  }
}
int main()
{
   char *p[3] = {"hello", "world", "kitty"}; // p[0] p[1] p[2]是 char*
   printf("%c\n", *p[0]);
   printf("%s\n", p[0]);
   fun(p, 3); // p就是&p[0],是char**
   return 0;
}
输出:
  h
hello
p[0]=hello
p[1]=world
p[2]=kitty
   当我们访问时p[0],我们得到字符串第一个字符的地址"hello".当我们使用*运算符,我们得到字符串第一个字符的值,即字符'h'.因此*p[0]等于字符'h',而不是字符串"hello".

指针作为函数的返回值

char *fun()
{
   char str[100] = "hello world";
   return str;
}
int main()
{
   char *p;
   p = fun();
   printf("p=%s\n", p);
   return 0;
}
在fun()函数退出,内存分配给str被释放,并且指向的指针str变得无效。因此,访问指向的内存p在main()函数之后fun()完成将导致未定义的行为.

若要解决此问题,可以使用以下方法动态分配内存调用函数然后应该释放分配的内存fun().
char *fun() {
   char *str = malloc(100);
   strcpy(str, "hello world");
   return str;
}

int main() {
   char *p;
   p = fun();
   printf("p=%s\n", p);
   free(p); // free the memory allocated by fun()
   return 0;
}
在此修改后的代码中,strcpy().然后,它返回指向已分配内存的指针。在main()函数,使用指向的字符串后p,则使用 释放分配的内存free().
//总结:返回地址的时候,地址指向的内存的内容不能释放。
如果返回的指针指向的内容已经被释放了,返回这个地址,也没有意义了。
解决方法一:返回静态局部数组的地址,因为静态数组的内容在函数调用结束后依然存在
static char str[100]="hello world";
使用静态变量:将str声明为静态变量,这样它将在整个程序的生命周期内存在。但是请记住,静态变量在多线程环境中可能会导致问题。

解决方法二:
   返回文字常量区的字符串的地址,因为文字常量区的内容一直存在。
   char *fun()
{
   char *str= "hello world";
   return str;
}
int main()
{
   char *p;
   p = fun();
   printf("p=%s\n", p);
   return 0;
}

解决方法三:
   返回堆内存的地址,因为堆区的内容一直存在,直到free才释放。
   char *fun()
{
   char *str;
   str = (char *)malloc(100);
   strcpy(str, "hello world");
   return str;
}
int main()
{
   char *p;
   p = fun();
   printf("p=%s\n", p);
   free(p);
   return 0;
}  

指针保存函数的地址(函数指针)

1.函数指针的概念

函数在运行程序的时候会将函数的指令加载到内存的代码段,所以函数也有起始地址。

c语言规定:函数的名字就是函数的首地址,即函数的入口地址,这样就可以定义一个指针变量,来存放函数的地址。这个指针变量就是函数指针变量。

2.函数指针的用处:

函数指针用来保存函数的入口地址。

在项目开发中,我们经常需要编写或者调用带函数指针参数的函数。

比如Linux系统中创建多线程的函数,它有个参数就是函数指针,接收线程函数的入口地址.

函数指针变量的定义

返回值类型(*函数指针变量名)(形参列表);

需要注意的是,函数指针变量名要使用括号将*与变量名括起来,这是因为在C语言中,*的优先级比()低,如果不使用括号,编译器会将该声明解释为返回值为指针类型的函数,而不是指向函数的指针。

int add(int a, int b) {
   return a + b;
}

int main() {
   int (*ptr)(int, int); // 定义一个函数指针变量 ptr
   ptr = add; // 将函数 add 的地址赋给指针 ptr

   int result = ptr(2, 3); // 使用指针调用函数
   printf("Result: %d\n", result);

   return 0;
}

调用函数的方法

1.通过函数名调用函数(常用)

2.通过函数指针变量调用

int add(int a, int b)
{
   return a + b;
}

int main()
{
   int (*ptr)(int a, int b); // 定义一个函数指针变量 ptr
   ptr = add;                // 将函数 add 的地址赋给指针 ptr

   int result = (*ptr)(3, 6); // 使用指针调用函数
   printf("Result: %d\n", result);
   result = ptr(6, 6);        // 使用指针调用函数
   printf("Result: %d\n", result);

   return 0;
}

(*ptr)(3, 6);和 ptr(6, 6);这两种方式是等效的,都可以用来调用函数

函数指针数组

由若干个相同类型的函数指针变量构成的集合,在内存中连续的顺序存储。

函数指针数组是个数组,它的每个元素都是一个函数指针变量。

函数指针数组的定义:

类型名(*数组名[元素个数])(形参列表)
//eg:int(*p[5])(int,int);定义了一个函数指针数组,有5个元素p[0]~p[4],每个元素都是函数指针变量,其指向的函数必须有整型的返回值,两个整型参数。
int add(int a, int b)
{
   return a + b;
}
int max(int x,int y)
{
   int temp;
   if(x>y)
       temp=x;
   else
       temp=y;
   return temp;
}
int min(int x,int y)
{
   int temp;
   if(x<y)
       temp=x;
   else
       temp=y;
   return temp;
}
int main()
{
   int (*p[3])(int,int)={min,max,add};
   int a=(*p[1])(1,2);
   printf("%d\n",a);
   a=p[2](2,3);
   printf("%d\n",a);
   return 0;
}
输出:
   2
5
(*p[1])(1,2); 和 p[2](1,2);等效,都可用来调用函数

经常容易混淆的指针概念

第一组: 1.int *a[10];

这是个指针数组,数组a中有10个整型的指针变量a[0]~a[9],每个元素都是 int*类型的指针变量

2.int (*a)[10];

数组指针变量,存地址编号,它指向一个数组。+1指向下个数组。

3.int **p;

指针的指针,保存指针变量的地址。

int **p;
int *q;
p=&q;
/**/
int **p;
int *q[10];
p=&q[0];//等价于p=q;    

第二组: 1.int *f();

声明这个函数返回值为int*类型

2.int(*f)();

是个函数指针变量,存放函数的地址

特殊指针

1.空类型指针(void*)

void*通用指针,任何类型的地址都可以给void *类型的指针变量赋值。

int *p;;

void *q;

q=p;是可以的,不用强制类型转换

2.NULL

空指针:

char *p=NULL;

可以认为p哪里都不指向,也可以认为p指向内存编号为0的存储单位。一般NULL用在给指针变量初始化。

main函数传参:

int main(int argc,char* argv[])
{
   printf("argc=%d\n",argc);
   for(int i=0;i<argc;i++)
  {
       printf("argv[%d]=%s\n",i,argv[i]);
  }
}
//CMD终端运行

c:\Users\r7ftf\Desktop\Ctest>dir
驱动器 C 中的卷没有标签。
卷的序列号是 1EAF-0AE7

c:\Users\r7ftf\Desktop\Ctest 的目录

2023/07/25  18:30    <DIR>         .
2023/07/25  18:30    <DIR>         ..
2023/07/08  10:54    <DIR>         .vscode
2023/07/18  19:37               510 a.c
2023/07/18  19:36            54,716 a.exe
2023/07/25  18:30               219 sz.c
2023/07/25  18:30            54,024 sz.exe
2023/07/14  12:26             1,656 tat1.c
2023/07/12  20:48            58,850 tat1.exe
2023/07/24  12:02            54,063 tempCodeRunnerFile.exe
2023/07/08  11:46               646 test.c
2023/07/08  11:46            55,063 test.exe
              9 个文件        279,747 字节
              3 个目录 15,230,459,904 可用字节

c:\Users\r7ftf\Desktop\Ctest>sz.exe aaa bbb ccc ddd eee
argc=6
argv[0]=sz.exe
argv[1]=aaa
argv[2]=bbb
argv[3]=ccc
argv[4]=ddd
argv[5]=eee
在C语言中,main函数可以接受命令行参数。通常情况下,main函数有两个标准形式:

int main(void):表示main函数不接受任何命令行参数。
int main(int argc, char *argv[]):表示main函数接受两个参数,分别是整数类型的argc和字符指针数组类型的argv。

命令行参数的使用有以下几个常见的用途:

  1. 提供配置信息:通过命令行参数,可以在程序运行时提供一些配置信息,例如文件路径、端口号、调试标志等。这样可以避免在每次运行程序时都需要手动修改代码。

  2. 控制程序行为:命令行参数可以用来控制程序的行为,例如通过传递不同的选项参数来选择执行不同的功能或算法。这种方式使得程序更灵活和可定制。

  3. 批量处理数据:当需要对多个输入文件进行批量处理时,可以将文件名作为命令行参数传递给程序。程序可以循环遍历处理每个文件,从而实现批量操作。

  4. 脚本集成:命令行参数使得程序可以与脚本语言(如Shell脚本)进行集成,从而通过脚本控制程序的执行流程和参数传递。

  5. 帮助和版本信息:通过命令行参数,可以实现显示程序使用帮助信息和版本信息的功能。当用户在命令行下输入特定参数时,程序可以显示相应的帮助文档或版本号。

总之,命令行参数提供了一种在程序运行时接收外部输入的方式,并且可以根据这些输入来完成不同的任务和控制程序的行为。它使得程序更具灵活性和可配置性,方便与其他工具和脚本集成。

动态内存申请

动态分配内存是指在程序运行时根据需要动态地申请和释放内存空间。这种内存管理技术允许程序在运行过程中根据实际需求来调整内存的大小,以优化资源利用和提高程序性能。

在静态分配内存中,程序员需要在编写代码时确定每个变量或数据结构所需的内存大小,并在编译时为其分配内存空间。这种方式的缺点是,内存空间的分配是固定的,无法根据程序的运行时需求进行动态调整,导致内存浪费或者不足的问题。

与静态分配相比,动态分配内存提供了更大的灵活性和效率。当程序需要更多内存时,可以使用动态内存分配函数(如malloc()、new等)向操作系统请求适当大小的内存块。如果内存不再需要,可以使用相应的释放函数(如free()、delete等)将内存归还给操作系统。

动态分配内存的主要优点包括:

  1. 节省内存空间:动态分配内存使得程序只使用实际需要的内存空间,避免了内存的浪费。

  2. 灵活性和可扩展性:程序可以根据实际需求动态调整内存大小,满足不同场景下的变化需求。

  3. 提高性能:动态分配内存可以在程序运行时按需申请和释放内存,避免了静态内存分配的固定开销,从而提高了程序的性能和效率。

然而,动态分配内存也存在一些潜在风险和需要注意的问题,如内存泄漏、野指针等。因此,在进行动态内存分配时,程序员需要谨慎管理内存的分配和释放,确保正确地使用和释放分配的内存空间。

内存泄漏和野指针是在动态内存分配和释放过程中常见的问题。

  1. 内存泄漏(Memory Leak):当动态分配的内存空间在不再需要时没有正确释放,就会导致内存泄漏。这意味着程序无法再重新使用该内存,导致内存占用不断增加,最终耗尽可用内存资源。内存泄漏可能会导致程序性能下降、崩溃或异常结束。要避免内存泄漏,确保在使用完毕后使用对应的释放函数(如free()、delete等)释放动态分配的内存。

  2. 野指针(Dangling Pointer):当一个指针指向已经释放的内存空间,或者指向未分配的内存空间,就会产生野指针。使用野指针可能导致程序崩溃、数据损坏或安全漏洞。要避免野指针,可以在释放内存后将指针设置为null或者初始化为合适的值。

为了避免内存泄漏和野指针,以下是一些建议:

  • 在使用动态内存分配函数(如malloc()、new等)申请内存后,始终确保使用相应的释放函数(如free()、delete等)进行内存释放。

  • 在释放内存后,将指针设置为null或初始化为合适的值,以避免出现野指针。

  • 避免多次释放同一块内存,确保释放的内存与分配的内存一致。

  • 使用合适的数据结构和算法来管理动态内存,避免不必要的内存分配和释放。

  • 使用内存管理工具和调试器来检测和解决内存泄漏和野指针问题。

通过遵循良好的内存管理和程序设计原则,可以最大程度地避免内存泄漏和野指针等内存相关问题。

静态分配,动态分配

静态分配:

1.在程序编译或运行过程中,按事先规定大小分配内存空间的分配方式。eg:int a[10]

2.必须事先知道所需空间的大小。

3.分配在栈区或全局变量区,一般以数组的形式。

4.按计划分配。

动态分配:

1.在程序运行过程中,根据需要大小自由分配所需空间。

2.按需分配。

3.分配在堆区,一般使用特定的函数进行分配。

动态分配函数

包含头文件stdlib.h

1.malloc函数

函数原型:void *malloc(unsigned int size);

功能说明:

在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。 函数原型返回void*指针,使用时必须做相应的强制类型转换,分配的内存空间内容不确定,一般使用memset初始化。

返回值:

分配空间的起始地址(分配成功)

NULL (分配失败)

注意:

1.在调用malloc之后,一定要判断一下,是否申请内存成功。

2.如果多次malloc申请的内存,第一次和第二次申请的内存不一定是连续的。

2.free函数(释放内存函数)

头文件:#include<stdlib.h>

函数定义:void free(void *ptr)

函数说明:free函数释放ptr指向的内存

注意:ptr指向的内存必须是malloc calloc relloc动态申请的内存

char *p=(char *)malloc(100);
free(p);
free后,又没有给p赋值,所以p还是指向原先动态申请的内存。但是内存已经不能再用了,p变成野指针了。
一块动态申请的内存只能free一次,不能多次free。

3.calloc函数

头文件:#include<stdlib.h>

函数定义:void* calloc(size_t nmemb,size_t size);

size_t 实际是无符号整型,它是在头文件中用typedef定义出来的。

函数的功能:在内存的堆中,申请nmemb块,每块的大小为size个字节的连续区域。

函数的返回值:

返回 申请内存的首地址 (申请成功)

返回 NULL (申请失败)

注意:

malloc和calloc 函数都是用来申请内存的。

区别: 函数名称、参数个数不一样。

malloc申请的内存中存放的内容是随机的,不确定的,而calloc 函数申请的内存中的内容为0。

char* p=(char *)calloc(3,100);
//在堆中申请了3块,每块大小为100个字节,即300个字节连续的区域。

4.realloc函数(重新申请内存)

头文件:#include<stdlib.h>

函数的声明:void * realloc(void *s,unsigned int newsize);

函数的功能:

在原先s指向的内存基础上重新申请内存,新的内存的大小为new_size个字节,如果原先内存后面有足够大的空间,就追加,如果后边的内存不够用,则realloc函数会在堆区找一个newsize个字节大小的内存申请,将原先内存中的内容拷贝过来,然后释放原先的内存,最后返回新的内存地址。

如果newsize比原先的内存小,则会释放原先内存的后面的存储空间,只留前面的newsize个字节。

返回值:新申请的内存的首地址。

char *p;
p=(char*)malloc(100);// 分配100个字节的内存空间
p=(char*)realloc(p,50); // 将内存大小重新调整为50个字节,100字节后50个字节的存储空间就被释放了。

注意:

malloc calloc realloc 动态申请的内存,只有在free或程序结束的时候才会被释放。

内存泄露

申请的内存,首地址丢了,找不到,没法使用,也没法释放,这块内存就被泄露了。

char *p;
p=(char *)malloc(100);
p="hello world";//p指向别的地方了,申请的内存,首地址丢了,则动态申请的100个字节就被泄露了。
void fun()
{
   char *p;
   p=(char*)malloc(100);
}
int main()
{
   fun();
   fun();
   return 0;
}
//每调用一次fun()泄露100个字节
解决方法一:
   void fun() {
   char *p;
   p = (char*)malloc(100);
   free(p); // 释放内存块
}

int main() {
   fun();
   fun();
   return 0;
}

解决方法二:
   char* fun()
{
   char* p;
   p=(char*)malloc(100);
   return p;
}
int main()
{
   char*q;
   q=fun();
   //可以通过q使用动态申请的100个字节的内存了
   free(q);//释放内存
return 0;
}

总结:申请的内存,一定不要把首地址给丢了,在不用的时候一定要释放内存。

测字符串长度函数

头文件:#include<string.h>

函数声明:size_t strlen(const char*s);

函数功能:

测字符指针s指向的字符串中字符的个数,不包括'\0'

返回值:字符串中字符个数

#include<stdio.h>
#include<string.h>
int main()
{
   char str1[20]="hello";
   char* str2="hello";
   printf("sizeof(str1)=%d\n",sizeof(str1));
   printf("sizeof(str2)=%d\n",sizeof(str2));
   printf("strlen(str1)=%d\n",strlen(str1));
   printf("strlen(str2)=%d\n",strlen(str2));
   return 0;
}
结果:
   sizeof(str1)=20
sizeof(str2)=8
strlen(str1)=5
strlen(str2)=5
sizeof 是个关键字,测量数据的占用内存空间大小。
如果测量的是数组名字,则测的是数组占多少个字节。
如果sizeof测的是指针变量,则测的是指针变量本身占几个字节,32位平台结果为4,64位平台结果为8。
strlen是个库函数,它测的是字符指针指向的字符串中字符的个数,不管指针是数组的名字,还是指针变量。

字符串拷贝函数

头文件:#include<string.h>

函数的声明:char *strcpy(char *dest,const char *src);

函数的说明:

拷贝src指向的字符串到dest指针指向的内存中,'\0'也会拷贝。

函数的返回值:目的内存的地址。

注意:在使用此函数的时候,必须保证dest指向的内存空间足够大,否则会出现内存污染。

 char str[100]="aaaaa0baaaaaaaaaaaa";
   strcpy(str,"hello");
   printf("str=%s\n",str);
   printf("str+5=%s\n",str+6);
结果:
   str=hello
str+5=baaaaaaaaaaaa
   strcpy拷贝的时候会把'\0'也拷贝。
char *strncpy(char*dest,const char *src,size_t n);
函数说明:
   将src指向的字符串前n个字节,拷贝到dest指向的内存中。
返回值:目的内存的首地址。
注意:strncpy不拷贝'\0'
   如果n大于src指向的字符串中的字符个数,则在dest后面填充n-strlen(src)个'\0'.
char str[100]="aaaaa0baaaaaaaaaaaa";
   strncpy(str,"helloworld",5);
   printf("str=%s\n",str);
   printf("str+5=%s\n",str+5);
结果:
   str=hello0baaaaaaaaaaaa
str+5=0baaaaaaaaaaaa
  // strncpy不拷贝'\0'
    char str[100]="aaaaa0baaaaaaaaaaaa";
   strncpy(str,"helloworld",15);
   printf("str=%s\n",str);
   printf("str+5=%s\n",str+5);
结果:
   str=helloworld
str+5=world
   //如果n大于src指向的字符串中的字符个数,则在dest后面填充n-strlen(src)个'\0'.

字符串追加函数

头文件:#include<string.h>

函数声明:char *strcat(char *dest,const char *src);
函数功能:
   strcat函数追加src字符串到dest指向的字符串的后面。追加的时候会追加'\0'。
注意:保证dest指向的内存空间足够大。  
   
   
char str[100] = "aa\0baaaaaaaaaaaaa";
   char *src = "hello";
   strcat(str, src);
   printf("str=%s\n", str);
结果:
   str=aahello
   //src是追加到'\0'后面。

char*strncat(char*dest,const char *src,size_t n);
追加src指向的字符串的前n个字符,到dest指向的字符串的后面。
注意:如果n大于src的字符个数,则只将src字符串追加到dest指向的字符串的后面,追加的时候会追加'\0'。
char str[100] = "aa\0baaaaaaaaaaaaa";
   char *src = "hello";
   strncat(str, src,3);
   printf("str=%s\n", str);
结果:
   str=aahel
   //从'\0'追加,追加的时候会追加'\0'。

字符串比较函数

头文件:#include<string.h>
函数声明:int strcmp(const char*s1,const char *s2);
函数说明:
   比较s1和s2指向的字符串的大小,
   比较方法:逐个字符去比较ASCII码,一旦比较出大小返回。
   如果所有字符都一样,则返回0
返回值:
   如果s1指向的字符串大于s2指向的字符串 返回1
   如果s1指向的字符串小于s2指向的字符串 返回-1
   如果相等则返回0
char *str1 = "hello world";
   char *str2 = "hello kitty";
   int ret = strcmp(str1, str2);
   if (ret > 0)
       printf("ret=%d str1大于str2", ret);
   else if (ret < 0)
       printf("ret=%d str1小于str2", ret);
   else
       printf("ret=%d str1等于str2", ret);
结果:
   ret=1 str1大于str2
int strncmp(const char* str1,const char *str2,size_t n);
函数说明:比较s1和s2指向的字符串中的前n个字符。
char *str1 = "hello world";
   char *str2 = "hello kitty";
   int ret = strncmp(str1, str2,5);
   if (ret > 0)
       printf("ret=%d str1大于str2", ret);
   else if (ret < 0)
       printf("ret=%d str1小于str2", ret);
   else
       printf("ret=%d str1等于str2", ret);
结果:
   ret=0 str1等于str2

字符查找函数

头文件:#include<string.h>
函数定义:char *strchr(const char *s,int c);
函数说明:在字符指针s指向的字符串中,找ascii码为c的字符。
注意是首次匹配,如果s指向的字符串中有多个ASCII为c的字符,则找的是第一个字符。
返回值:
   找到了 返回找到的字符地址
   没找到返回 NULL
char *str="helloworldhello";
  char *p=strchr(str,'o');
  if(p==NULL)
  {
   printf("没找到\n");
   return 0;
  }
  printf("p-str=%d\n",p-str);
结果:
   p-str=4
函数声明:char *strrchr(const char *s,int c);
函数说明:末次匹配
在s指向的字符串中,找最后一次出现的ASCII为c的字符。
返回值:
   末次匹配的字符的地址。
   找不到则返回NULL
   char *str="helloworldhello";
  char *p=strrchr(str,'e');
  if(p==NULL)
  {
   printf("没找到\n");
   return 0;
  }
  printf("p-str=%d\n",p-str);
结果:
   p-str=11

字符串匹配函数

#include<string.h>
char *strstr(const char *haystack,const char *needle);
函数说明:
   在haystack指向的字符串中查找needle指向的字符串,也是首次匹配
   返回值:
   找到的字符串的首地址
   否则返回NULL.
   
char str1[100] = "fghdfgdh$#$sfjshfjshf$#$djfldldso";
   char str2[100] = "$#$";
   char *p;
   p = strstr(str1, str2);
   if (p == NULL)
  {
       printf("没找到\n");
       return 0;
  }
   printf("p-str1=%d\n", p - str1);
结果:
   p-str1=8

空间设定函数

头文件:#include<string.h>
函数声明:void *memset(void *ptr,int value,size_t num);

函数功能:
   memset函数是将ptr指向的内存空间的num个字节全部赋值为value
参数:
   ptr:指向任意类型的指针,即指向我们需要修改的内存
   value:给ptr所指的内存中的num个字节全部用value代替
   返回值:
        目的内存的首地址,即ptr的值
char str[100]="helloworld";
  printf("str=%s\n",str);
  memset(str,'a',5);
  printf("str=%s\n",str);
结果:
   str=helloworld
str=aaaaaworld
   一般用于初始化内存

字符串转换数组

atoi/atol/atof //字符串转换功能

头文件:#include<stdlib.h>
函数声明:int atoi(const char *nptr);
函数的功能:
   将nptr指向的字符串转换成整数,返回
返回值:
   转换后的整数,此值由将输入字符作为数字解析而生成。如果该输入无法转换为该类型的值,则atoi的返回值为0
   int num;
   num=atoi("123");
   printf("num=%d\n",num);
num=atoi("abc");
   printf("num=%d\n",num);
结果:
   num=123
   num=0
long atol(const char*nptr);//返回长整型
double atof(const char*nptr);//返回浮点型

字符串切割函数

头文件:#include<string.h>

函数声明:char *strtok(char *str,const char *delim);
函数功能:
   字符串切割,按照delim指向的字符串中的字符,q切割str指向的字符串。
   其实就是在str指向的字符串中发现了delim字符串中的字符,就将其变成'\0',调用一次strtok只切割一次,切割一次之后,再去切割的时候strtok的第一个参数传NULL,意思是接着上次切割的位置继续切。
注意:
   如果srt字符中出现了连续的几个delim中的字符,则只将第一个字符变成'\0'
char str[100]="xiaoming:21,,,.男.女,北京:haidian";
   char*p[7];
   int i=0;
   printf("str=%s\n",str);
   p[i]=strtok(str,":,.");
   printf("p[%d]=%s\n",i,p[i]);
   printf("str=%s\n",str);
   while(p[i]!=NULL)
  {
       
       p[i]=strtok(NULL,":,.");
       printf("p[%d]=%s   ",i,p[i]);
       i++;
  }
结果:
   str=xiaoming:21,,,.男.女,北京:haidian
p[0]=xiaoming
str=xiaoming
p[0]=21   p[1]=男   p[2]=女   p[3]=北京   p[4]=haidian  

       
int msg_deal(char *msg_src, char *msg_done[], char *str)
{
   int i = 0;
   msg_done[i] = strtok(msg_src, str);
   while (msg_done[i] != NULL)
  {
       i++;
       msg_done[i] = strtok(NULL, str);
  }
   return i;
}
int main()
{
   char s[] = "+CMGR:REC UNREAD,+8613466630259,98/10/01,18:22:11+00,ABCdefGHI";
   char *p[6];
   int num = msg_deal(s, p, ",");
   printf("num=%d\n", num);
   for (int i = 0; i < num; i++)
  {
       printf("p[%d]=%s\n", i, p[i]);
  }

   return 0;
}
结果:
   num=5
p[0]=+CMGR:REC UNREAD
p[1]=+8613466630259
p[2]=98/10/01
p[3]=18:22:11+00
p[4]=ABCdefGHI

格式化字符串操作函数

int sprintf(char *buf,const char *format,...);
//输出到buf指定的内存区域。
eg:
char buf[20];
sprintf(buf,"%d:%d:%d",2013,10,1);
printf("buf=%s\n",buf);



int sscanf(const char *buf,const char *format,...);
//从buf指定的内存区域中读入信息
eg:
int a,b,c;
sscanf("2013:10:1","%d:%d:%d",&a,&b,&c);
printf("%d %d %d\n",a,b,c);

sscanf高级用法

1.跳过数据:%*s,%*c或%*d
eg:sscanf("1234 5678","%*d%s",buf);
2.读指定宽度的数据:%[width]s
eg:sscanf("12345678","%5s",buf);
3.集合操作:只支持获取字符串
%[a-z]表示匹配a到z中任意字符(尽可能多的匹配)
%[aBbc]//匹配[]里的字符,贪婪性
%[^aFc]//匹配除aFc的任意字符,贪婪性
%[^a-z]//匹配除a-z以外的所有字符
注意:空格也是一个字符
//练习:
   使用sscanf获取#@号之间的字符串abc#def@ghi
char buf[20];
sscanf("asdf#sdjd@djfkd","%*[^#]%*c%[^@]",buf);  

const关键字:

1.修饰普通变量,代表只读的意思。

const int a=100;//定义了一个只读变量a值为100以后在程序中,不能再给a赋值了。

2.const修饰指针

1.const char *str//str指向的内存的内容不能通过str来修改,用来保护str指向的内存的内容。
但是str的指向是可以改变的
2.char *const str//意思是str是只读的变量,str不能指向别的地方,但是str指向的内存的内容是有可能可以修改的。
3.const char * const str
 //str不能指向别的地方,指向的内存的内容也不能通过str去修改。
"%02d" 表示将整数格式化为两位数的字符串,并在单个数字的情况下在前面填充零。
"%2d" 表示将整数格式化为两位数的字符串,但不会在单个数字的情况下填充零。
对于大多数情况下,这两个占位符都可以达到相同的效果。但是需要注意的是,如果要保持一致性和对齐性,通常建议使用"%02d",因为它能够确保生成的字符串始终具有相同的宽度。

结构体

结构体是一种构造数据类型,它是由若干个相同或不同类型的数据构成的集合。

结构体类型定义

在使用结构体之前必须先有类型,然后用类型定义数据结构,这个类型相当于一个模具。

1.先定义结构体类型再定义结构体变量

struct 结构体类型名{

成员列表;

};

//有了结构体类型后,就可以用类型定义变量。
struct stu{
   int num;
   char name[23];
   char sex;
};//不要忘了分号
struct stu lucy,bob,lilei;//定义了三个struct stu类型的变量,每个变量都有三个成员,分别是num name sex。

2.在定义结构体类型的时候顺便定义结构体变量,以后还可以定义结构体变量。

struct 结构体类型名{

成员列表;

}结构体变量 1,变量 2;

struct 结构体类型名 变量3,变量4

struct stu{
   int num;
   char name[20];
   char sex;
}lucy,bob,lilei;

struct stu xiaohong,xiaoming;

3.在定义结构体类型的时候,没有定义结构体类型名,顺便定义结构体变量,因为没有类型名,所以以后不能再定义相关类型的数据了。

struct {

成员列表;

}变量1,变量2;

struct {
   int num;
   char name[20];
   char sex;
}lucy,bob;
//以后没法再定义这个结构体类型的数据了,因为没有类型名。

4.最常用的方法

通常我们将一个结构体类型重新起个类型名,用新的类型名替代原先的类型。

typedef struct stu{
   int num;
   int name[20];
   char sex;
}STU;//STU就相当于struct stu
以后STU就相当于 struct stu
STU lucy;和 struct stu lucy;是等价的。

结构体变量的定义初始化及使用

结构体变量,是个变量,这个变量是若干个相同或不同数据构成的集合。

注:在定义结构体变量之前首先得有结构体类型,然后再定义变量。

在定义结构体变量的时候,可以顺便给结构体变量赋初值,被称为结构体的初始化。

结构体变量初始化的时候,各个成员顺序初始化。

struct stu{
   int num;
   char name[20];
   char sex;
};
struct stu boy;
struct stu lucy={
   101,
   "lucy",
   'f'
};

结构体变量成员的引用方法

结构体变量.成员名

struct stu{
   int num;
   char name[20];
   char sex;
 
   
};
struct stu bob;
bob.num=101;//bob是个结构体变量,bob.num是个int类型的变量

struct stu
{
   int num;
   char name[20];
   char sex;
   char * addr;
};

int main()
{
   struct stu boy;
   boy.num = 1001;
   // boy.name="bob"; 错误,因为bob.name是个数组的名字,是个常量。
   strcpy(boy.name, "bob");
   printf("boy.num=%d\n", boy.num);
   printf("boy.name=%s\n", boy.name);
}
//strcpy(boy.addr,"beijing");错误,bob.addr是个野指针。
boy.addr="beijing";
printf("bob.addr=%s\n",boy.addr);

结构体成员多级引用

struct date{
   int year;
   int month;
   int day;
} ;
struct stu{
   int num;
   char name[20];
   char sex;
   struct date birthday;
};


eg:
struct date
{
   int year;
   int month;
   int day;
};
struct stu
{
   int num;
   char name[20];
   char sex;
   struct date birthday;
};

int main()
{
   struct stu boy = {1001, "lilei", 'm'};
   boy.birthday.day = 1;
   boy.birthday.month = 3;
   boy.birthday.year = 2000;
   printf("%d %s %c\n", boy.num, boy.name, boy.sex);
   printf("%d %d %d", boy.birthday.year, boy.birthday.month, boy.birthday.day);

   return 0;
}

相同类型的结构体变量可以相互赋值

#include <stdio.h>
#include <string.h>

struct stu {
   int num;
   char name[20];
};

int main() {
   struct stu student1;
   struct stu student2;

   student1.num = 1001;
   strcpy(student1.name, "Alice");

   student2 = student1;  // 将student1的值赋给student2

   printf("student2.num=%d\n", student2.num);
   printf("student2.name=%s\n", student2.name);

   return 0;
}

结构体数组

结构体数组是个数组,由若干个相同类型的结构体变量构成的集合。

1.结构体数组的定义方法

struct 结构体类型名 数组名[元素个数];
struct stu{
   int num;
   char name[20];
   char sex;
};
struct stu edu[3];//定义了一个struct stu 类型的结构体数组edu,这个数组有3个元素分别是edu[0] edu[1] edu[2]

1.结构体数组元素的引用 数组名[下标]

2.数组元素的使用

edu[0].num=101;//用101给edu数组的第0个结构体变量的num赋值
strcpy(edu[1].name,"lucy");
typedef struct student{
   int num;
   char name[20];
   float score;
}STU;


int main()
{
   STU edu[3]={
      {101,"a",78},
      {102,"b",59.5},
      {103,"c",88}
  };
   float sum=0;
   for(int i=0;i<3;i++)
  {
       sum=sum+edu[i].score;
  }
   printf("平均成绩:%f\n",(float)(sum/3));

   return 0;
}

结构体指针

即结构体的地址,结构体变量存放内存中,也有起始地址。定义一个变量存放这个地址,这个变量就是结构体指针变量。

结构体指针变量也是个指针

1.结构体指针变量的定义

struct 结构体类型名 * 结构体指针变量名;
struct stu{
   int num;
   char name[20];
};
struct stu*p;//定义了一个struct stu*类型的指针变量
变量名是p,p占4个字节,用来保存结构体变量的地址编号。
struct stu boy;
p=&boy;
访问结构体变量的成员方法:
boy.num=101;//结构体变量名.成员名
(*p).num=101;//*p相当于p指向的变量boy
p->num=101;//指针->成员名
注意:
   通过结构体指针来引用指针指向的结构体成员,前提是指针必须先指向一个结构体变量。
   

结构体指针应用场景:

1.保存结构体变量的地址

typedef struct stu{
   int num;
   char name[20];
   float score;
}STU;
int main()
{
   STU *p,lucy;
   p=&lucy;
   p->num=101;
   strcpy(p-name,"baby");
   //p->name="baby";错误,因为p->name相当于lucy.name是个字符数组的名字是个常量。
}

2.传 结构体变量的地址

typedef struct stu{
   int num;
   char name[20];
   float score;
}STU;
void fun(STU *p)
{
   p->num=101;
  (*p).score=88.6f;
   strcpy(p->name,"lucy");
}
int main()
{
   STU girl;
   fun(&girl);
   printf("%d %s %f\n",girl.num,girl.name,girl.score);
   return 0;
}

3.传 结构体数组的地址

结构体数组,是由若干个相同类型的结构体变量构成的集合。存放在内存里,也有起始地址,其实就是第0个结构体变量的地址。

typedef struct stu
{
   int num;
   char name[20];
   float score;
} STU;
void fun(STU *p, int n)
{
   for (int i = 0; i < n; i++)
  {
       printf("%d %s %f\n", p[i].num, p[i].name, p[i].score);
  }
}
int main()
{
   STU edu[3] = {
      {101, "lucy", 89.2f}, {102, "bob", 78}, {103, "lik", 58}};
   fun(edu,3);

   return 0;
}
注意:
   结构体变量的地址编号和结构体第一个成员的地址编号相同,但指针的类型不同。
   &boy//STU*
   &boy.num//int*
   结构体数组地址就是结构体数组中第0个元素的地址。
   edu//STU*
   &edu[0]//STU*

结构体内存分配

规则1:以多少个字节为单位开辟内存

给结构体变量分配内存的时候,会去结构体变量中找基本类型的成员,哪个基本类型的成员占字节数多,就以它的大小为单位开辟内存,在gcc中出现了double类型的,列外。

1.成员中只有char型数据,以1字节为单位开辟内存。

2.成员中出现了short int类型数据,没有更大字节数的基本类型数据,以2字节为单位开辟内存。

3.出现了int float 没有更大字节的基本类型数据的时候以4字节为单位开辟内存。

4.出现了double类型的数据:

情况1:在vc6.0和visual studio中以8字节为单位开辟内存。

情况2:在Linux环境gcc里,以4字节为单位开辟内存(开辟2次)。

无论是哪种环境,double型变量占8字节。

注意:

如果在结构体中出现了数组,数组可以看成多个变量的集合。

如果出现指针的话,没有占字节数更大的类型的,以4字节为单位开辟内存。

在内存中存储结构体成员的时候,按定义的结构体成员的顺序存储。

规则2:字节对齐

1.char 1字节对齐,即存放char型的变量,内存单元编号是1的倍数即可。

2.short int 2字节对齐,即存放short int 型的变量,起始内存单元的编号是2的倍数即可。

3.int 4字节对齐,即存放int型的变量,起始内存单元的编号是4的倍数即可

4.long int在32位平台下,4字节对齐,即存放long int型的变量,起始内存单元的编号是4的倍数即可。

5.float 4字节对齐,即存放float型的变量,起始内存单元的编号是4的倍数即可。

6.double:在vc6.0和visual studio环境下8字节对齐,即存放double型变量的起始地址,必须是8的倍数,double变量占8字节。

gcc环境下:4字节对齐,即存放doubel型变量的起始地址,必须是4的倍数,double变量占8字节。

struct stu{
   char a;
   short int b;
   int c;
}temp;
int main()
{
   printf("%d\n",sizeof(temp));
   printf("%p\n",&(temp.a));
   printf("%p\n",&(temp.b));
   printf("%p\n",&(temp.c));
   return 0;

}//temp内存占8个字节
结果:
   8
0000000000407970
0000000000407972
0000000000407974
struct stu{
   char a;
   int c;
   short int b;
}temp;
int main()
{
   printf("%d\n",sizeof(temp));
   printf("%p\n",&(temp.a));
   printf("%p\n",&(temp.b));
   printf("%p\n",&(temp.c));
   return 0;

}//temp内存占12个字节
结果:
   12
0000000000407970
0000000000407978
0000000000407974

为什么要有字节对齐?

用空间来换时间,提高cpu读取数据的效率

指定对齐原则:

使用#pragma pack改变默认对齐原则

格式:

#pragma pack(value)时的指定对齐值value。

注意:

1.value值只能是:2的n次方 1 2 4 8等。

2.指定对齐值与数据类型对齐值相比取较小值。

说明:以多少个字节为单位开辟内存取决于结构体成员中,占字节数最大的类型长度和value比较,取较小值,为单位开辟内存。

#pragma pack(2)
struct stu{
   int a[2];
   double c;
   short int b;
}temp;
int main()
{
   printf("%d\n",sizeof(temp));
   printf("%p\n",&(temp.a));
   printf("%p\n",&(temp.b));
   printf("%p\n",&(temp.c));
   return 0;

}
结果:
   18
0000000000407970
0000000000407980
0000000000407978
#pragma pack(2)
struct stu{
   char a,b,c;
}temp;
int main()
{
   printf("%d\n",sizeof(temp));
   printf("%p\n",&(temp.a));
   printf("%p\n",&(temp.b));
   printf("%p\n",&(temp.c));
   return 0;

}
结果:
   3
0000000000407970
0000000000407971
0000000000407972

 

位段

在结构体中,以位为单位的成员,称之为位段(位域)。

struct packed_data{
   unsigned int a:2;
   unsigned int b:6;
   unsigned int c:4;
   unsigned int d:4;
   unsigned int i;
}data;
注意:
   不能对位段成员取地址。
struct packed_data
{
   unsigned int a : 2;
   unsigned int b : 6;
   unsigned int c : 4;
   unsigned int d : 4;
   unsigned int i;
} temp;
int main()
{
   printf("%d\n", sizeof(temp));
   printf("%p\n", &temp);
   printf("%p\n", &temp.i);
   return 0;
}
结果:
   8
0000000000407970
0000000000407974

位段注意:

1.对于位段成员的引用如下:

data.a=2赋值时,不要超出位段定义的范围;如段成员a定义为2位,最大值为3即(11二进制)所以data.a=5就会取5(101)的低两位进行赋值。

 temp.a = 2; // 10(二进制)
   printf("temp.a=%u\n", temp.a);//temp.a=2
   temp.a = 5; // 101(二进制)
   printf("temp.a=%u\n", temp.a);//01 temp.a=1

 

2.位段成员的类型必须指定为整型或字符型

3.一个位段必须存放在一个存储单元中,不能跨两个单元,第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。

位段的存储单元:

char 型位段 存储单元是1个字节。

short int 型位段 存储单元是2个字节。

int 型位段 存储单元是4个字节

long int 型位段 存储单元是4个字节(具体大小取决于编译器和平台)

double 类型的位段通常存储在 8 个字节中

struct stu{
   char a:7;
   char b:7;
   char c:2;
}temp;//占3字节,b不能跨存储单元存储
//如果挨着存占2个字节(7bit+7bit+2bit)/8bit=2(byte)字节。

 

位段成员的宽度必须小于等于其数据类型的宽度。
不同编译器可能对位段的实现有所不同,包括位域的顺序、字节对齐和位域之间的填充方式。
位段不能具有地址,因此无法对其进行取址操作。
位段的性能和可移植性可能受到硬件和编译器的影响,因此在使用位段时需要谨慎考虑兼容性问题。
位段(Bitfields)是一种C语言特性,用于指定结构体成员使用特定数量的位来存储数据。在位段中,可以将一个或多个成员声明为具有固定位宽的字段。

使用位段的主要目的是在内存使用和访问上节省空间。由于某些数据类型(如布尔值或状态标志)仅需要存储少量的位,使用完整的字节或更大的数据类型可能会浪费内存。通过使用位段,可以按照精确的位宽来分配内存,从而提高内存效率。

4.位段的长度不能大于存储单元的长度

char 型位段不能大于8位(bit)

short int 型位段不能大于16位(bit)

int 型位段不能大于32位(bit)

long int 型位段不能大于32位(bit)

5.如一个段要从另一个存储单元开始可以定义:

unsigned char a:1;
unsigned char b:2;
unsigned char :0;
unsigned char c:3;(在另一个单元)
由于用了长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放。
//将a,b存储在一个存储单元中,c另存在下一个单元。    

共用体

1.共用体和结构体类似,也是一种构造类型的数据结构。既然是构造类型的,得先定义出类型,然后用类型定义变量。

定义共用体类型的方法和结构体非常相似,把struct 改成 union就可以了。

在进行某些算法的时候,需要使用几种不同类型的变量存到同一段内存单元中,几个变量所使用空间相互重叠。

这种几个不同的变量共同占用一段内存的结构,在c语言中,被称作“共用体”类型结构

共用体所有成员占有同一段地址空间。

共用体的大小是其占内存长度最大的成员的大小。

typedef union data{
   short int i;
   char ch;
   float f;
}DATA;
DATA temp2;
//共用体temp2占4个字节,即i,ch,f共用4个字节。
typedef struct stu1
{
   int a;
   int b;
   int c;
} STU1;
typedef union stu2
{
   int a;
   int b;
   int c;
} STU2;
int main()
{
   STU1 temp1;
   STU2 temp2;
   printf("temp1=%d\n", sizeof(temp1));
   printf("temp2=%d\n", sizeof(temp2));
     printf("&temp1.a=%p\n", &temp1.a);
   printf("&temp1.b=%p\n", &temp1.b);
   printf("&temp1.c=%p\n", &temp1.c);
   printf("&temp2.a=%p\n", &temp2.a);
   printf("&temp2.b=%p\n", &temp2.b);
   printf("&temp2.c=%p\n", &temp2.c);
    printf("&temp2=%p\n",&temp2);
   
   return 0;
}
结果:
   temp1=12
   temp2=4
   &temp1.a=000000000061FE14
&temp1.b=000000000061FE18
&temp1.c=000000000061FE1C
&temp2.a=000000000061FE10
&temp2.b=000000000061FE10
&temp2.c=000000000061FE10
   &temp2=000000000061FE10

共用体的特点:

1.同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用。

2.共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖。

3.共用体变量的地址和它的各个成员的地址都是同一地址。

4.共用体变量的初始化

union data a={123};//初始化共用体只能为第一个成员赋值,不能给所有成员都赋初值。
typedef union data
{
   unsigned char a;
   unsigned int b;
} DATA;

int main()
{
   DATA temp;
   temp.b = 0xffffffff;
   printf("temp.b=%x\n", temp.b);
   printf("temp.a=%x\n", temp.a);
   temp.a = 0x55;
   printf("temp.b=%x\n", temp.b);
   printf("temp.a=%x\n", temp.a);
   return 0;
}
结果:
   
   temp.b=ffffffff
temp.a=ff
temp.b=ffffff55
temp.a=55

枚举

将变量的值一 一列举出来,变量的值只限于列举出来的值的范围内。

枚举类型也是构造类型,类型定义类似结构体类型的定义。

使用枚举的时候,得先定义枚举类型,再定义枚举变量。

1.枚举类型的定义方法

enum 枚举类型名{

枚举值列表;

};

在枚举值表中应列出所有可用值,也称为枚举元素,枚举元素是常量,默认是从0开始编号的。

枚举变量仅能取枚举值所列元素。

2.枚举变量的定义方法

enum 枚举类型名 枚举变量名;

enum week{
   mon=3,tue,wed,thu,fri=4,sat,sun
};

int main()
{
   enum week workday;
   workday=fri;
   printf("workday=%d\n",workday);
   printf("mon=%d\n",mon);
   printf("tue=%d\n",tue);
   printf("wed=%d\n",wed);
   printf("thu=%d\n",thu);
   printf("fri=%d\n",fri);
   printf("sat=%d\n",sat);
   printf("sun=%d\n",sun);

   return 0;
}
结果:
   workday=4
mon=3
tue=4
wed=5
thu=6
fri=4
sat=5
sun=6

文件

文件就是用来存放程序,文档,音频,视频数据,图片等数据的。

文件就是存放在磁盘上的,一些数据的集合。

文件的分类:

磁盘文件:(我们通常认识的文件)

指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存。

设备文件:

在操作系统中把每一个与主机相连的输入,输出设备看作是一个文件,把它们的输入,输出等同于对磁盘文件的读和写。

键盘:标准输入文件

屏幕:标准输出文件

其他设备:打印机,触摸屏,摄像头,音响等。

在Linux操作系统中,每一个外部设备都在/dev目录下对应着一个设备文件,要想在程序中操作设备,就必须对与其对应的/dev目录下的设备文件进行操作。

全缓冲

标准IO库函数往普通文件写数据的是全缓冲。

刷新缓冲区的情况:

1.缓冲区满了,刷新缓冲区。

2.调用函数刷新缓冲区fflush(文件指针)。

3.程序结束 会刷新缓冲区。

磁盘文件的分类:

一个文件通常是磁盘上一段命名的存储区

计算机的存储在物理上是二进制的,所以物理上的所有磁盘文件本质上都是一样的:

以字节为单位进行顺序存储

从用户或操作系统使用的角度(逻辑上)

把文件分为:

文本文件:基于字符编码的文件,常见编码有ASCII,UNICODE等,一般可以使用文本编辑器直接打开。

列如:数5678的ASCII存储形式为:

ASCII码:00110101 00110110 00110111 00111000

二进制文件:基于值编码的文件,根据具体应用,指定某个值是什么意思,一般需要自己判断或使用特定软件分析数据格式。

列如:数5678的存储形式为:

二进制码:0001011000101110

音频文件(mp3):二进制文件

图片文件(bmp)文件,一个像素点由两个字节来描述*****######&&&&&,565

*代表红色的值R

#代表绿色的值G

&代表蓝色的值B

二进制文件以位来表示一个意思。

文本文件,二进制文件对比:

译码:

文本文件编码基于字符定长,译码容易些;

二进制文件编码是变长的,译码难一些(不同的二进制文件格式,有不同的译码方式,一般需要特定软件进行译码)。

空间利用率:

二进制文件用一个比特来代表一个意思(位操作);而文本文件任何一个意思至少是一个字符。所以二进制文件,空间利用率高。

可读性:

文本文件用通用的记事本工具就几乎可以浏览所有文本文件

二进制文件需要一个具体的文件解码器,比如读BMP文件,必须用读图软件。

总结:

1.文件在硬盘上存储的时候,物理上都是用二进制来存储的。

2.标准io库函数,对文件操作的时候,不管文件的编码格式(字符编码,或二进制),而是按字节对文件进行读写,所以管文件又叫流式文件,即把文件看成一个字节流。

文件指针

文件指针在程序中用来标识(代表)一个文件,在打开文件的时候得到文件指针,文件指针就用来代表打开的文件。

对文件进行读,写,关闭等操作的时候,对文件指针进行操作即可,即将文件指针传给读,写,关闭等函数,那些函数就知道要对哪个文件进行操作。

定义文件指针的一般形式为:

FILE * 指针变量标识符;

FILE 为大写,需要包含<stdio.h>

FILE 是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构体中含有文件名,文件状态和文件当前位置等信息

一般情况下,我们操作文件前必须定义一个文件指针 ,标识 我们将要操作的文件

实际编程中使用库函数操作文件,无需关心FILE结构体的细节,只需要将文件指针传给io库函数,库函数再通过FILE结构体里的信息对文件进行操作。

FILE 在stdio.h文件中的文件类型声明:
   typedef struct{
       short level;//缓冲区'满'或'空'的程度
       unsigned flags;//文件状态标志
       char fd;//文件描述符
       unsigned charhold;//如无 缓冲区不读取字符
       short bsize;//缓冲区的大小
       unsigned char *buffer;//数据缓冲区的位置
       unsigned char *curp;//指针,当前的指向
       unsigned istemp;//临时文件,指示器
       short token;//用于有效性检查
  }FILE
       

在缓冲文件系统中,每个被使用的文件都要在内存中开辟一块FILE类型的区域,存放与操作文件相关的信息。

对文件操作的步骤:

1.对文件进行读写等操作之前要打开文件得到文件指针

2.可以通过文件指针对文件进行读写等操作

3.读写等操作完毕后,要关闭文件,关闭文件后,就不能再通过此文件指针操作文件了

补充: c语言中有三个特殊的文件指针无需定义,在程序中可以直接使用

stdin:标准输入 默认为当前终端(键盘)

我们使用的scanf,getchar函数默认从此终端获得数据

stdout:标准输出 默认为当前终端(屏幕)

我们使用的printf,puts函数默认输出信息到此终端

stderr:标准错误输出设备文件 默认为当前终端(屏幕)

当我们程序出错使用:perror函数时信息打印在此终端

总结: 文件指针是个指针,它是个FILE类型结构体指针,用文件指针来标识一个文件。

打开文件 fopen

函数的声明:
FILE *fopen(const char *path,const char *mode);
函数说明:
   fopen函数的功能是打开一个已经存在的文件,并返回这个文件的文件指针(文件的标识)或者创建一个文件,并打开此文件,然后返回文件的标识。
函数的参数:
   参数1:打开文件的路径
   1.绝对路径,从根目录开始的路径名称 eg:"D:\\dome\\test\\aaa.txt"
   2.相对路径 eg:".\\test\\aaa.txt"
    参数2:文件打开的方式,即以什么样的方式(只读,只写,可读可写等等)打开文件
       
第二个参数的几种形式(打开文件的方式) 读写权限:r w a +
       r:以只读方式打开文件,文件不存在返回NULL;文件存在,且打开文件成功,返回文件指针,进行后续的读操作。
       eg:FILE *fp;
fp=fopen("test.txt","r");//以只读方式打开文件
fp=fopen("test.txt","rb");//同样是以只读方式打开文件但是以二进制方式打开。

w:以只写方式打开文件,文件不存在,以指定文件名创建此文件,并打开文件,若文件存在,清空文件内容,打开文件,然后进行写操作;如果文件打不开(比如文件只读),返回NULL。
        a:以追加方式打开文件,文件不存在,以指定文件名创建此文件(同w),若文件存在,从文件的结尾处进行写操作。
说明:如果不加a的话,打开文件的时候读写位置在文件的开始,对文件进行读写的时候都是从文件开始进行读写的。如果加a,打开已经存在的文件,读写位置在文件的末尾。
        +:同时以读写打开指定文件
           
r+或rb+:以可读可写的方式打开文件(并不创建新文件)
w+或wb+:以可读可写的方式打开文件(使文件长度为0字节,创建一个文件)
a+或ab+:以添加方式打开文件,打开文件并在文件末尾更改文件(如果文件不存在,则创建文件)
         
返回值:
成功:打开文件对应的文件指针
失败:返回NULL
以后调用fopen函数的时候,一定要判断一下,打开是否成功。

关闭文件 fclose

函数的头文件:#include<stdio.h>
函数的声明:int fclose(FILE *fp);
函数的说明:关闭fp所代表的文件,注意一个文件只能关闭一次,不能多次关闭。关闭文件之后就不能再用文件指针对文件进行读写等操作了。
返回值:
   成功返回0
   失败返回非0
   可以通过返回值,来判断关闭文件是否成功。
   
int main()
{
   FILE* fp1;
   int ret;
   fp1=fopen(".\\test001.txt","a+");
   if(fp1==NULL)
  {
       printf("打开文件失败\n");
       perror("fopen");
       return 0;
  }
   printf("打开文件成功\n");
   ret=fclose(fp1);
   if(ret==0)
  {
       printf("关闭文件成功\n");
  }
   else
  {
       printf("关闭文件失败\n");
  }
   return 0;
}
结果:
   打开文件成功
   关闭文件成功

一次读写一个字符

函数声明:
int fgetc(FILE *stream);
函数说明:
   fgetc从stream所标识的文件中读取一个字节,将字节值返回。
返回值:
   以t(文本)的方式:读到文件结尾返回EOF
   以b(二进制)的方式:读到文件结尾,使用feof(文件指针)判断结尾
   feof是c语言标准库函数,其原型在stdio.h中,其功能是检测文件流上的文件结束符,如果文件结束则返回非0值,否则返回0(即,文件结束:返回非0值;文件未结束:返回0值)。
   
函数声明:
int fputc(int c,FILE *stream);
函数的说明:  
fputc将c的值写到stream所代表的文件中。
返回值:
   如果输出成功,则返回输出的字节值;
   如果输出失败,则返回一个EOF。
   EOF是在stdio.h文件中定义的符号常量,值为 -1
   注意:
   打开文件的时候,默认读写位置在文件的开始,如果以a的方式打开读写位置在文件的末尾,在向文件中读取字节或写入字节的时候,读写位置会往文件的末尾方向偏移,读写多少个字节,读写位置就往文件的末尾方向偏移多少个字节。  
#include <stdio.h>

int main() {
   FILE* fp1;
   char ch;
   fp1 = fopen(".\\test001.txt", "a+");  // 以可读写追加方式打开文件
   if (fp1 == NULL) {  // 检查文件是否成功打开
       printf("打开文件失败\n");
       perror("fopen");
       return 0;
  }
   while ((ch = fgetc(fp1)) != EOF) {  // 逐个字符从文件读取,直到文件结束
       fputc(ch, stdout);  // 将字符输出到屏幕(标准输出)
  }
   fclose(fp1);  // 关闭文件
   return 0;
}
#include <stdio.h>

int main() {
   FILE* fp1;
   FILE* fp2;
   char ch;
   fp1 = fopen(".\\test001.txt", "a+");  // 以可读写追加方式打开test001.txt文件
   fp2 = fopen(".\\test002.txt", "a+");  // 以可读写追加方式打开test002.txt文件
   if (fp1 == NULL) {  // 检查test001.txt文件是否成功打开
       printf("打开文件失败\n");
       perror("fopen");
       return 0;
  }
   while ((ch = fgetc(fp1)) != EOF) {  // 逐个字符从test001.txt文件读取,直到文件结束
       fputc(ch, stdout);  // 将字符输出到屏幕(标准输出)
       fputc(ch, fp2);  // 将字符写入到test002.txt文件中
  }
   fclose(fp1);  // 关闭test001.txt文件
   fclose(fp2);  // 关闭test002.txt文件
   return 0;
}

一次读写一个字符串

char *fgets(char *s,int size,FILE *stream);
从stream所指向的文件中读取字符,在读取的时候碰到换行符或者是碰到文件的末尾停止读取,或者是读取了size-1个字节停止读取,在读取的内容后面会加一个\0,作为字符串的结尾。

返回值:
   成功返回目的数组的首地址,即s
   失败返回NULL
int fputs(const char *s,FILE *stream);
函数功能:
   将s指向的字符串,写到stream所代表的文件中
返回值:
   成功返回写入的字节数
   失败返回-1  
#include <stdio.h>

int main() {
   FILE* fp1;
   FILE* fp2;
   char ch[200];
   fp1 = fopen(".\\test001.txt", "a+");  // 以可读写追加方式打开test001.txt文件
   fp2 = fopen(".\\test002.txt", "a+");  // 以可读写追加方式打开test002.txt文件
   if (fp1 == NULL) {  // 检查test001.txt文件是否成功打开
       printf("打开文件失败\n");
       perror("fopen");
       return 0;
  }
   fgets(ch, 6, fp1);  // 从test001.txt文件读取最多6个字符(包括换行符),存储到ch数组中
   fputs(ch, fp2);  // 将ch数组中的内容写入到test002.txt文件中
   printf("ch=%s\n", ch);  // 将读取的字符数组输出到屏幕上
   fclose(fp1);  // 关闭test001.txt文件
   fclose(fp2);  // 关闭test002.txt文件
   return 0;
}

读文件 fread

函数的声明:
   size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
函数的说明:
   fread函数从stream所标识的文件中读取数据,每块是size个字节,共nmemb块,存放到ptr指向的内存里
返回值:
   实际读到的块数。
   
   eg:
unsigned int num;
num=fread(str,100,3,fp);//从fp所代表的文件中读取内容放到str指向的内存中,读取的字节数为300,每块100个字节共3块。
返回值:num
如果读到300个字节返回值num为3
如果读到了大于等于200个字节小于300个字节返回值为2
读到的字节数大于等于100小于200个字节返回值为1
不到100个字节返回0

写文件 fwrite

函数声明:
   size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);
函数说明:
   fwrite函数将ptr指向的内存里的数据,向stream所标识的文件中写入数据,每块是size个字节,共nmemb块。
返回值:
   实际写入的块数
注意:
fwrite函数是将内存中的数据原样输出到文件中。
fread函数是将文件中的数据原样读取到内存里。  

随机读写

前面介绍的对文件的读写方式都是顺序读写,即读写文件只能从头开始,顺序读写各个数据;但在实际问题中通常要求只读写文件中某一指定的部分,列如:读取文件第200~300个字节。

为了解决这个问题可以移动文件内部的位置指针到需要读写的位置,再进行读写,这种读写称为随机读写。

实现随机读写的关键是按要求移动位置指针,这称为文件的定位。

完成文件定位的函数有:

rewind,fseek函数

1.rewind 复位读写位置

rewind函数
   void rewind(文件指针);
函数功能:
   把文件内部的位置指针移动到文件首
   调用形式:
   rewind(文件指针);

2.ftell函数测文件读写位置距文件开始有多少个字节

ftell测文件读写位置距文件开始有多少个字节
定义函数:
   long ftell(文件指针);
函数功能:
取得文件流目前的读写位置。
返回值:
   返回当前读写位置(距离文件起始的字节数),出错时返回-1。
  long int length;
   length=ftell(fp);

3.fseek 定位位置指针(读写位置)

fseek函数(一般用于二进制文件即打开文件方式需要带b)
函数声明:int fseek(FILE *stream,long offset,int whence);
//int fseek(文件类型指针,位移量,起始点);
函数功能:
   移动文件流的读写位置。
参数:
  whence 起始位置
   文件开头     SEEK_SET 0
   文件当前位置  SEEK_CUR 1
   文件末尾     SEEK_END 2
偏移量:
   以起始点为基点,向前,后移动的字节数,正数往文件末尾方向偏移,负数往文件开头方向偏移。
   

fseek(fp, 50, SEEK_SET);
这个调用将文件指针相对于文件开头向前移动 50 个字节的位置。

fseek(fp, -50, SEEK_END);
这个调用将文件指针相对于文件末尾向后移动 50 个字节的位置。

fseek(fp, 0, SEEK_END);
这个调用将文件指针移动到文件末尾。

fseek(fp, 20, SEEK_CUR);
这个调用将文件指针相对于当前位置向前移动 20 个字节的位置。
 

标签:文件,int,char,str,printf,指针
From: https://www.cnblogs.com/ptm2/p/17595542.html

相关文章

  • C语言---malloc(0)会产生什么结果,真的是空指针吗?
    前言(1)几天前在一个交流群中看到有人说,面试问malloc(0)会怎么样是真的恶心。(2)这个突然激起了我的好奇心。居然还可以malloc(0)?!(3)经过测试最后,发现是可行的。经过互联网的查找,肯哥的交流群以及自己的理解,梳理成这篇博客。(4)肯哥博客主页:架构师李肯;(5)感慨一下,群里面的大佬们不愧是有......
  • 指针1
    #define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>//指针的详细学习//指针就是变量,用来存放地址的变量,存在指针里面的变量被称为地址。//不同类型的指针可以储存各自数据,不过在解引用的时候会出现问题//因此指针类型决定了指针进行解引用的时候能够访问空间的大小,int*p,*p......
  • Go语言中指针详解
    指针在Go语言中是一个重要的特性,它允许你引用和操作变量的内存地址。下面是指针的主要作用和相关示例代码:1.引用传递在Go中,所有的变量传递都是值传递,即复制变量的值。如果你想在一个函数中修改一个变量的值,并希望这些改变在函数外部也有效,你就需要使用指针。通过传递一个变......
  • C# 使用SIMD向量类型加速浮点数组求和运算(4):用引用代替指针, 摆脱unsafe关键字,兼谈Unsa
    作者:zyl910目录一、引言二、办法说明2.1历史2.2局部引用变量与引用所指的值(类似指针的地址运算符&、间接运算符*)2.3重新分配局部引用变量(类似指针直接赋值)2.4引用地址调整(类似指针加减法)2.5引用地址比较(类似指针比较)2.6重新解释(类似C++的reinterpret_cast)2.7引用取消只......
  • 【Java】使用fastjson进行序列化时出现空指针异常问题研究
    最近在使用fastjson的JSONObject.toJSONString()方法将bean对象转为字符串的时候报如下错误:com.alibaba.fastjson.JSONException:writejavaBeanerror,fastjsonversion1.2.58,classcom.sun.proxy.$Proxy395,fieldName:0 atcom.alibaba.fastjson.serializer.JavaBeanS......
  • 指针面试题2
    &aa拿到二维数组的地址,+1跳过整个二维数组,强制转换为整型指针,放到ptr1中,*(ptr-1)则为10aa为二维数组首元素也就是首个一维数组的地址,aa+1为第二个一维数组的地址,解引用再强制转换为整型指针,放入ptr2中,ptr2-1指向元素5,解引用就是5char*a[]为应该指针数组,数组里面放有三个指针指向三个......
  • 第三章 指针才是C语言的精髓(嵌入式Linux与物联网软件开发 C语言内核深度解析)
    这仅仅是读《嵌入式Linux与物联网软件开发:C语言内核深度解析》pdf的知识记录 地址:指的都是某个字节的地址。比如inta的空间大小有4个字节,每个字节都有一个地址(也就是有4个地址),但是只有首字节地址才能作为整个a空间的地址。也就是说,整个内存以1个字节为基本单位划分无数个地址,......
  • golang打印指针切片/数组的值
     FmtSlice2String方法可以将指针切片的值打印处理packagemainimport( "fmt" "reflect")typeStudentstruct{ Namestring`json:"name"cn:"名字"` Ageuint64`json:"age"cn:"年龄"`}funcmain(){ s:=mak......
  • EnableHeaderCheckBox导致空指针
    GridViewCheckBoxColumn以CheckBox的形式显示、编辑bool值,实现行选中效果。如果需要在表头添加全选框,可以将EnableHeaderCheckBox设置为true:privatevoidAddCheckColumn(){checkColumn=newGridViewCheckBoxColumn();checkColumn.Name="Select";checkColum......
  • 指针DAY3
    指针3指针和多维数组  代码:#include<stdio.h>intmain(){intC[3][2][2]={{{2,5},{7,9}},{{3,4},{6,1}},{{0,8},{11,13}}};printf("%d%d%d%d\n",C,*C,C[0],&C[0][0]);p......