首页 > 其他分享 >c语言笔记6

c语言笔记6

时间:2023-08-26 16:12:16浏览次数:53  
标签:文件 语言 int 笔记 char 结构 变量 struct

c语言笔记6(结构体,共用体,枚举,文件操作,makefile)

1. 结构体

1.1 结构体的概念

结构体也是构造类型之一,由至少一个基本数据类型或构造类型组成的一种数据结构(集合),这种数据结构称之为结构体

1.2 结构体的定义

使用结构体之前,先定义结构体,然后使用这个结构体时作为一种数据类型(构造类型)

语法1:只声明结构体,【推荐使用】

struct 结构体类型的名称{
    //声明成员变量,成员变量不要初始化(值)
    int sid;
    char name[32];
};

结构体变量的定义:struct结构体类型名 变量名;

结构体类型的变量的初始化:{按成员顺序从上到下依次赋值}

结构体类型的变量的访问:变量名.成员名

语法2:声明结构体时,同时声明结构体类型的变量

【注】结构类型的变量的内存空间随着定义而创建(栈),结构体变量的内存空间大小由成员变量决定的。

struct 结构体类型的名称{
    //声明成员变量,成员变量不要初始化(值)
    ...
}变量名,...;

语法3:一次性声明结构体,因为没有结构体类型名,所以无法再定义新的结构体变量

struct {
    //声明成员变量
}变量;

1.3 定义结构体时指定新类型名

语法:

typedef struct 结构体类型名{
    成员变量;
}大写字母的新类型名称;

1.4 结构体变量赋值

1.4.1 从键盘输入数据

结构体中成员变量是可以取地址的

typedef struct stu
{
    int sid;
    char name[32];
}STU;
int main()
{
    STU s;
    scanf("%d %s",&s.sid,s.name);
}

1.4.2 清空数据

可以通过memset()清空结构体变量中成员的数据

memset(&结构体类型的变量名,0,sizeof(struct 结构类型名或typedef定义的新类型名));
typedef struct stu
{
    int sid;
    char name[32];
}STU;
int main()
{
    STU s;
    memset(&s,0,sizeof(STU));//先清空,再使用
    scanf("%d %s",&s.sid,s.name);
}

1.4.3 结构体的成员是指针

如果结构体的成员是指针时,可以直接赋值(常量区的地址)或者可以从堆区中分配空间并向内存空间写数据(strcpy)

struct Data
{
    int n;
    char *name;
};
int main()
{
    struct Data1 d1 = {1,"lucy"};
    printf("%d %s\n",d1.n,d1.name);
    
    struct Data d2;
    d2.n=100;
    //字符数组名 不能直接赋值,因为数组名是常量
    //d2.name = "jack";//指针变量可以指向其他内容,可以修改的
    d2.name = (char*)malloc(32);
    strcpy(d2.name,"lucy");
    printf("%d %s\n",d2.n,d2.name);
    free(d2.name);//手动释放,否则会内存泄露
    return 0;
}

结构体变量中的成员部分在栈区(结构体变量是在栈区分配空间),当结构体变量释放(自动释放,离开变量的作用域),堆空间需要手动释放

1.4.4 结构体嵌套赋值

给嵌套的结构体变量赋值时,由外向内引用,逐层引用

struct lrc_time
{
    char hour;
    char minute;
    char second;
};
typedef struct lrc
{
    struct lrc_time start_time;
    char *content;
}LRC;
int main ()
{
    //定义结构体变量并初始化值
    LRC line1 = {{0,0,2},"abc..."};
    
    LRC line2;
    line2.start_time.hour=0;
    line2.start_time.minute=1;
    line2.start_time.second=15;
    line2.content="bbb...";
}

1.4.5 结构体变量赋值引用

两个相同的结构体类型的变量可以相互的赋值(浅拷贝)。

浅拷贝:复制结构变量的内存空间(只复制第一层)

如:

struct user_s
{
    int uid;
    char *name;
};
struct goods_s
{
    int gid;
    float price;
    char *name;
};
struct order_s
{
    struct user_s user;
    struct goods_s goods;
    float order_price;
    int num; 
    char pay_status; 
}
int main()
{
    struct user_s user1={1,"小明"};
    struct user_s user2={2,"小红"};
    struct goods_s goods1={201,6000.0f,"苹果"};
    struct goods_s goods2={202,5000.0f,"华为"};
    struct order_s order1={user1,goods1,6000.0f,1,1};
    struct order_s order2=order1;
    order2.user.name="小王";
    order2.goods.name="小米";
}

浅拷贝:只复制结构体变量的内存空间,不会复制结构体指针指向的堆空间

深拷贝:将结构体变量的内存空间及所有层次的指针指向的空间也复制出来

1.5 结构体数组

语法:

struct 结构体类型名 变量名[个数];

引用:同数组操作。

struct POS
{
    int x;
    int y;
};
int main()
{
    struct POS two_pos[2];
    memset(&two_pos[0],0,sizeof(struct POS));
    memset(&two_pso[1],0,sizeof(struct POS));
    for(int i=0;i<2;i++)
    {
        printf("输入点坐标:x y");
        scanf("%d %d",&two_pos[i].x,&two_pos[i].y);
    }
}

1.6 结构体指针

结构体指针可以指向已存在的结构体变量,也可以在堆区动态申请空间

结构体指针变量 和一般的指针变量的用法相同

【注】结构体变量引用成员时,必须使用->替换.

语法:

struct 结构体类型名 *变量名;

结构体指针可以指向已存在的结构体变量

结构体指针的变量名 = &结构体变量;

在堆区动态申请空间

结构指针的变量名 = malloc(sizeof(struct 结构体类型名));
struct POS
{
    int x;
    int y;
}
int main()
{
    //struct POS p1 = {0,0};
    //struct POS *p2 = &p1;
    //p2->x=2;
    //p2->y=2;
    struct POS*p1=malloc(sizeof(struct POS));
    p1->x=(int*)malloc(sizeof(int));
    p1->y=(int*)malloc(sizeof(int));
    *(p1->x)=20;
    *(p1->y)=30;
    //回收堆内存,先结构体成员,再结构体
    free(p1->x);
    free(p1->y);
    free(p1);
}

2. 结构体内存分配

结构体变量大小是它所有成员之和

2.1 结构体内存分配

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

以成员占最大字节为单位分配的
1)只有一个char,按1B分配
2)只有一个short,按2B
3)只有一个int,float,按4B
4)只有一个long,double 按8B(64位操作系统)或按4B(32位操作系统)

【注意】

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

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

规则2:字节对齐

1)确认结构体的分配单位(最大的基本数据类型的大小)
2)成员变量的起始地址(编号),是数据类型大小的整数倍数
3)自动对齐,按分配单位对齐
4)如果成员变量是数组时,可以看成是多个结构体的成员变量
5)分配内存是按从上到下按顺序依次分配

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

指定对齐原则:可以通过#pragma a pack(value)设置结构的分配单位 value只能是:1 2 4 8

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

2.2 位段

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

【注意】

1)不能对位段成员取地址

2)位段赋值时,不要超出位段定义的范围;

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

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

位段的存储单元:

(1)char       存储单元是1个字节
(2)short int  存储单元是2个字节
(3)int        存储单元是4个字节
(4)long int   存储单元是4或8字节

5)位段的长度不能大于存储单元的长度

6)如一个段要从另一个存储单元开始,可以定义位值为0,而且不能有成员名

例1:

struct
{
    char a:6;
    short b:15;
    int c:30;
}p1;//8B

例2:

struct
{
    char a:3;
    char :0;//下一个位段在新的存储单元中存储
    char c:3;
}p2;//2B

例3:

struct
{
    unsigned char a:2;
    unsigned char :1;//占位
    unsigned char b:2;//操作类型
    unsigned char :1;//占位
    unsigned char c:2;//操作类型
}p3;//1B

3. 共用体union

共用体也是一种构造类型的数据结构

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

几个不同的变量(成员)共同占用一段内存的结构

共用体中的成员占用同一段内存空间,共用体的大小是其占内存长度最大的成员的大小。

union data1_uni
{
    char a;
    short b;
    int c;
}data1;
void test()
{
    printf("%ld\n",sizeof(union data1_uni));//4
}

共用体的特点:

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

2.共用体变量中起作用的成员是最后一次操作的成员,在修改新的成员数据后原有的成员的值会被覆盖

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

4.共用体变量的初始化,只初始化第一个成员

【注】对共用体成员进行操作时,原则上操作哪一个成员,则用哪一个成员

4. 枚举

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

枚举也是个构造类型的,用法同struct,union相似

【注】

1)枚举中的成员都是一个常量名,可以在程序中直接使用的,而且在定义时可以给定顺序值(第一个成员常量默认为0)

2)枚举变量仅能取枚举值所列元素(常量)

//一般情况下,枚举的成员名采用全大写字母
typedef enum color_enum
{
    Gray=30,//默认第一个元素的常量值:0
    Red,
    Black,
    Green,
    Yellow,
    Blue
}Color;

【注】在定义枚举类型的时候枚举元素可以用等号给它赋值,用来代表元素从几开始编号在程序中,不能再次对枚举元素赋值,因为枚举元素是常量

5. 文件操作

5.1 文件的概念

文件用来存放程序、文档、音频、视频、图片等数据

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

硬盘文件类型:存储在外部介质(如磁盘)上

1)文本文件(记事本、文本编辑器,以字符为单位存储)
2)二进制文件(图片、音频、视频、office文件,以位为单位存储,读取内容时,需要特定格式的软件)

设备文件类型:操作系统中把每一个与主机相连的输入、输出设备看作是一个文件

1)标准输入设备stdin,键盘 fgets(buf,size,stdin);
2)标准输出设备stdout,控制台(终端)  fflush(stdout);
3)标准错误设备stderr,控制台(终端)

5.2 缓冲区的特点

io操作:输入(input)输出(output)操作

文件缓冲是库函数(c语言封装的函数)申请的一段内存,由库函数对其进行操作,程序员没有必要知道存放在哪里,只需要知道对文件操作的时候的一些缓冲特点即可。

1)行缓冲:写数据时遇到换行符,则刷新缓冲

int main(int argc,char const*argv[])
{
    printf("hello,world");
    while(1);
    return 0;
}

如果行缓冲不刷新时,则不会输出结果,因为输出的内容没有遇到换行符(Linux\n),Window(\r\n)。

2)缓冲区满了:自动刷新缓冲区

void test()
{
    char m[32]="abcdefghijklmnabcdefghijklmn";
    while(1)
        printf("%s",m);
    return 0;
}

3)手动刷新缓冲区:fflush(stdout)

void test()
{
    //缓冲区一般是8kb
    char m[33]='abcdefghijklmnabcdefghijklmn\0';
    printf("%s",m);
    fflush(stdout);
    while(1);
    return 0;
}

4)程序正常结束 会刷新缓冲区

void test()
{
    char m[33]='abcdefghijklmnabcdefghijklmn\0';
    printf("%s",m);
    return 0;
}

5.3 缓冲的分类

行缓冲:遇到换行符则刷新缓冲

全缓冲:

标准io库函数,往普通文件读写数据的,是全缓冲的

碰到换行符也不刷新缓冲区,只有缓冲区满了,才会刷新缓冲区

无缓冲:

使用系统调用的io函数操作文件时,直接与文件进行交互,中间没有缓冲区

【读写文件的流程】

应用程序空间->内核空间->驱动程序->硬盘上

应用程序和内核程序运行在不同的空间里,目的是为了保护内核。

5.4 文本文件与二进制的比较

译码

文本问价编码基于字符定长,译码容易些;
二进制文件编码是变长的,译码难一些(不同的二进制文件格式,有不同的译码方式) 

空间利用率

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

可读性

文本文件用通用的记事本工具就几乎可以浏览所有文本文件
二进制文件需要一个具体的文件解码器,比如读BMP文件,必须用读图软件

总结

文件在硬盘上存储的时候,物理上都是用二进制来存储的
标准io库函数,对文件操作的时候,不管文件的编码格式(字符编码,二进制),而是按字节对文件进行读写,所以文件又叫流式文件,即把文件看成一个字节流

5.5 文件操作的相关库函数

文件指针在程序中用来标识(代表)一个文件的,在打开文件的时候得到文件指针。对文件进行读、写、关闭等操作的时候,对文件指针进行操作即可。

FILE在stdio.h文件中的文件类型声明:

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

定义文件指针的一般形式为:FILE * 指针变量标识符

打开与关闭文件:
FILE * fopen (const char * path,const char *mode);
   path:打开文件的路径(相对的,绝对的)
   mode:打开文件的模式(r,r+,rb,rb+,w,w+,wb,wb+,a,a+,ab+)
   r开头的模式,只读或读写,文件不存在时,会报错
   w开头的模式,写写或读写,文件不存在时,会自动创建。w模式会清空文件内容
   a开头的模式,追加(写),文件不存在时,会自动创建。打开文件之后光标会移到内容的尾部。
   +模式和r,w,a组合使用,表示扩展读写的
   b模式和r,w,a组合使用,表示以二进制的格式(图片、pdf、音频)等。
打开文件成功时,返回文件指针,反正,返回NULL

int fclose(FILE*fp);关闭文件,成功返回0,反之非0(-1)

一次一个字符读和写:
int fgetc(FITL*stream);从文件中读取一个字符
      返回值:1)t模式:读到文件结尾返回EOF(符号常量 -1)
             2)b模式:读到文件结尾,使用feof()判断
int fputc(int c,FILE*stream);向文件中写一个字符。如果写入字节的值,反之返回EOF

一次一个字符串读和写:
char *fgets(char *s,int size,FILE *stream);
     从stream所指的文件中读取字符,在读取的时候碰到换行符或者是碰到文件的末尾停止读取,或者是读取了size-1个字节停止读取,在读取的内容后面会加一个\0,作为字符串的结尾。
     成功返回目的数组的首地址,即s
     失败返回NULL
int fputs(const char*s,FILE *stream);
     将s指向的字符串,写到stream所代表的文件中
     成功返回1,失败-1 EOF

按块的方式读和写:
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
      从stream文件中读取数据,一块是size个字节,共nmemb块,存放到ptr指向的内存空间。
size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);
       将ptr指向的内存空间的nmemb块的size的大小写入到文件中。

格式化写与读:
fprintf(FILE *file,char *format,...);
fscanf(FILE *file,char *format,...);

随机读取:
rewind(FILE *file);光标位置的复位
long ftell(FILE *file);获取当前光标的位置(相对于开始偏移了多少个字节),返回字节数
int fseek(FILE *stream,long offset,int whence);移动文件流的读写位置
      whence:
             文件开头 SEEK_SET 0
             文件当前位置 SEEK_CUR 1
             文件末尾 SEEK_END 2

6. makefile

6.1 make和makefile区别

make是个命令,是个可执行程序,用来解析Makefile文件的命令

makefile是个文件,这个文件中描述了项目资源的编译规则。

采用Makefile的好处

1)简化编译程序的时候输入的命令,编译的时候只需要敲make命令就可以了
2)可以节省编译时间,提高编译效率
【自动检查源文件哪些发生了变化,只有被修改的源文件才会再次编译】

6.2 make的概念

GNU make是一种代码维护工具,make工具会根据makefile文件定义的规则和步骤,完成整个软件项目的代码维护工作。

makefile文件:在Window上的集成开发环境下,会自动生成,在Linux下需要手动编写。

make主要解决两个问题:

1)大量代码的关系维护
2)减少重复编译时间

make的命令:自动从当前工作目录下找makefile文件(GNUmakefile、makefile、Makefile)

make 编译目标
make -f makefile文件名[makefile中第一个编译目标]

检查是否存在make命令:

which make
make --version

6.3 makefile语法规则

语法:

[变量名=变量值]
[...]
编译目标: [依赖文件列表]
<tab>命令

定义变量的方式:

在makefle编译目标的上面,可以定义一些变量,变量在下方使用时,可以通过$(变量名)或${变量名}引用。一个变量占一行

依赖文件列表

多个依赖文件使用空格分隔
一个编译目标可以不需要依赖文件,只是执行目标的命令

命令

编译c文件或库文件等常用Linux命令,如:gcc,ar,rm
【注】如果存在多个命令时,每一个命令占一行

项目编译的命令

make
make main
make clean

6.4 makefile中的变量

makefile中的变量可以在编译目标内的命令中使用

在makefile中的变量分三种:

1)环境变量
   在执行make命令编译makefile之前,会将系统的环境变量加载到makefile中
2)预定义变量
    是makefile中特有的变量:
    $@ 表示编译目标
    $< 表示依赖文件列表中的第一个
    $^ 表示依赖文件列表中去重之后的内容
3)自定义变量    

注意:1.变量名是大小写敏感的

​ 2.变量名一般都在makefile的头部定义(编译目标上方)

​ 3.变量名几乎可在makefile的任何地方使用

makefile中可以使用通配符:%(表示任意多或零个字符),%在编译的目标中使用时,可能会被执行多次,每一次一个目标。

如:

cc=gcc
obj=main.o link.o
main:$(obj)
    $(cc) $^ -o $@
%.o:%.c
    $(cc) -c $< -o $@
clean:
    rm -rf *.o main

6.5 makefile中常用的预定量

AR 归档维护程序的程序名,默认值是ar
ARFLAGS 归档维护程序的选项
AS 汇编程序的名称,默认值为as
ASFLAGS 汇编程序的选项
CC C 编译器的名称,默认为CC,软链接到/usr/bin/gcc上
CFLAGS C 编译器的选项
CPP C 预编译的名称,默认值为$(CC) -E
CPPFLAGS C 预编译的选项
CXX C++编译器的名称,默认值为g++
CXXFLAGS C++编译器的选项

标签:文件,语言,int,笔记,char,结构,变量,struct
From: https://www.cnblogs.com/dijun666/p/17658940.html

相关文章

  • 欧拉定理学习笔记
    欧拉定理:若\(gcd(a,m)=1\),则\(a^{\varphi(m)}\equiv1\pmod{m}\)证明:令\(r_1,r_2,···,r_{\varphi(m)}\)为模m下的一个简化剩余系,则\(ar_1,ar_2,···,ar_{\varphi(m)}\)也为模m下的一个简化剩余系,令\(f=r_1*r_2*···*r_{\varphi(m)}\),则有:\(f\equivar_1*ar_2*···*ar_{......
  • R语言之数据框的合并
    文章和代码已经归档至【Github仓库:<https://github.com/timerring/dive-into-AI>】或者公众号【AIShareLab】回复R语言也可获取。有时数据集来自多个地方,我们需要将两个或多个数据集合并成一个数据集。合并数据框的操作包括纵向合并、横向合并和按照某个共有变量合并。1.纵向合......
  • C语言实现顺序表
    顺序表本质上是数组,但是在数组的基础上,还要求数据是从头开始连续存储的,不能跳跃间隔。顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。顺序表分为静态顺序表和动态顺序表两种静态顺序表使用定长数组存储元素......
  • 2023-08-26:请用go语言编写。开心一下的智力题: 有一个村庄,一共250人, 每一个村民要么一
    2023-08-26:请用go语言编写。开心一下的智力题:有一个村庄,一共250人,每一个村民要么一定说谎,要么只说真话,村里有A、B、C、D四个球队,且每个村民只会喜欢其中的一支球队,但是说谎者会不告知真实喜好,而且会说是另外三支球队的支持者。访问所有的村民之后,得到的访谈结果如下:A的支持者有90......
  • 2023-08-26:请用go语言编写。开心一下的智力题: 有一个村庄,一共250人, 每一个村民要么一
    2023-08-26:请用go语言编写。开心一下的智力题:有一个村庄,一共250人,每一个村民要么一定说谎,要么只说真话,村里有A、B、C、D四个球队,且每个村民只会喜欢其中的一支球队,但是说谎者会不告知真实喜好,而且会说是另外三支球队的支持者。访问所有的村民之后,得到的访谈结果如下:A的支......
  • 杜教筛学习笔记
    杜教筛学习笔记闲话感觉以前根本没学懂杜教筛,于是重学了一遍,写个笔记记录一下。前置知识依赖于迪利克雷卷积、莫比乌斯反演、整除分块相关知识。记号约定及基本性质约定:\(f*g\)表示\(f\)与\(g\)的迪利克雷卷积,即\((f*g)(n)=\sum\limits_{ij=n}f(i)g(j)\)。\(f\cdot......
  • Linux设备驱动开发详解——学习笔记
    Linux设备驱动概述计算机系统的运转需要软件和硬件共同参与,硬件是底层基础,软件则实现了具体的应用。硬件和软件之间则通过设备驱动来联系。在没有操作系统的情况下,工程师可以根据硬件设备的特点自行定义接口。而在有操作系统的情况下,驱动的架构则由相应的操作系统来定义。驱动存......
  • Mybatis学习笔记
    一、Mybatis简介二、下载与实现1)下载 官网下载2)实现①创建项目工程,并加入依赖②创建核心configuration.xml配置文件,配置JDBC的连接信息③创建POJO对象④创建POJO对应的mapper映射文件⑤在configuration.xml文件中加载mapper文件⑥测试三、接口实现方式(项目中常用)①创建一个接口②......
  • c 语言 数组(一维)做函数参数
    @TOC前言函数参数:函数参数是函数内外连接的接口,可以互通数据。一、传递一维数组函数调用时,实参是给形参初始化,所以,实参传递什么类型的数据,形参就以什么类型去接住。比如一维数组,如下:函数fun1传递a,因为数组名就是数组的首地址,所以用int*p形参。函数fun2传递&a,是一维数组......
  • 运筹学习笔记之列生成
    列生成算法介绍1.什么是列生成列生成算法是一种用于解决大规模线性规划问题的高效算法,它基于单纯形法的思想,通过求解子问题来找到可以进基的非基变量。在列生成算法中,每个变量都代表一列,因此称为列生成算法。该算法的优点在于其高效的计算性能和较好的收敛性,适用于处理大规模、......