目的
1.学习结构体的创建,使用
2.指针的相关知识
3.实现对文件的读写
4.学会创建链表 以及链表的基本知识
技术及其使用
一.结构体的创建
//结构体 是一种数据类型 复合数据类型
//struct
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
struct Person {
char name[10];
char *addr;
int age;
};
//定义结构体struct Person变量 变量名xw
int i;
struct Person xw;
xw.name ="xiaowang";//数组一旦定义地址不能改变
//不能直接给数组赋值
xw.name[0]='x';//系统为这个变量分配内存了
xw.name[1]='w';
xw.addr ="西南大学";//常量字符串的地址是由系统分配的
//字符指针赋值 必须要有内存
xw.age =30;
int i1 =10;
struct Person zs = {"zhangsan",20};
//类比
printf("name:%s\n",xw.name);
printf("addr:%s\n",xw.addr);
return 0;
}
//'&'取某个变量的地址
二.结构体对齐的规则
1.第一个成员在与结构体变量偏移量(offset)为0的地址处。
2.其他成员变量要对齐到对齐数的整数倍的地址处。
3.对齐数 = 对齐系数 与 该成员大小的较小值。
4.VS中默认的值为8;linux中的默认值为4。
5.结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
6.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
1.下面的结构体大小分别是多大(假设32位机器)?
struct A {
char a; //1
char b; //1
char c; //1
};
//进行整体对齐,最大类型为1<对齐系数4,按1整体对齐,所以1+1+1=3
struct B {
int a; //4
char b; //1
short c;//2
};
//进行整体对齐,最大类型为4=对齐系数4,所以按4整体对齐4 1+2=3<4 对齐4 所以4+4=8
struct C {
char b; //1
int a; //4
short c;//2
};
//进行整体对齐,最大类型为4=对齐系数4,所以按4整体对齐 1<4(对齐4) 4=4 2<4(对齐4) 所以4+4+4=12
#pragma pack(2)
struct D {
char b; //1
int a; //4
short c;//2
};
//进行整体对齐,最大类型为4>对齐系数n(2),所以按2整体对齐 1<2(对齐2)4>2(2的倍数) 2=2 所以2+4+2=8
答案及解析:3 8 12 8
2. 有一个如下的结构体:
struct A{
long a1;
short a2;
int a3;
int *a4;
};
请问在64位编译器下用sizeof(struct A)计算出的大小是多少?
24
28
16
18
答案及解析:24
64位编译器下:指针8字节(一定不能忘记),题目不说默认4字节对齐
long a1; //8
short a2; //2 8+2=10(不是4的倍数)对齐到4的倍数12
int a3; //4 4+12=16(4的倍数)
int *a4; //8 8+16=24(4的倍数)
3.在32位cpu上选择缺省对齐的情况下,有如下结构体定义:
struct A{
unsigned a : 19;
unsigned b : 11;
unsigned c : 4;
unsigned d : 29;
char index;
};
则sizeof(struct A)的值为()
9
12
16
20
答案及解析16
题目不说,默认4字节对齐
19+11=30<32bit 4
4+29=33>32bit 4+4
1byte=8bit 1 对齐到 4
4+4+4+4=16
---------------------
来源:CSDN
原文:https://blog.csdn.net/kuimzzs/article/details/81605160
三.C语言指针
1.常用的指针类型
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
2.指针的值与指针指向的类型
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。
3.指针在32位系统中占4个字节.在64位系统中占8个字节
4.&与星号的区别
这里&是取地址运算符,星号是间接运算符。
&a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
p 的运算结果就五花八门了。总之p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
5.指针与数组
数组的数组名其实可以看作一个指针。看下例:
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0]; //也可写成:value=*array;
value=array[3]; //也可写成:value=*(array+3);
value=array[4]; //也可写成:value=*(array+4);
上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以(array+3)等于3。其它依此类推。
四.C语言的文件操作
回顾前面的知识,指针必须要有基类型(指向),如int p,floatp…。
那么顾名思义文件指针就是指向文件的,其实文件在C语言中是,一种结构体
也就是和结构体指针是一样的。
以下是文件结构体的定义,结构体的名称为FILE。
tpyedef struct
{ short level; //缓冲区满或空的程度
unsigned flags; //文件状态标志
char fd; //文件描述符
unsigned char hold; //如缓冲区无内容不读取字符
short bsize; //缓冲区的大小
unsigned char*buffer; //数据缓冲区的位置
unsigned char*curp; //文件位置标记指针当前的指向
unsigned istemp; //临时文件指示器
short token; //用于有效性检查
}FILE
//这是TC2.0中的定义
#ifndef _FILE_DEFINED
struct _iobuf {
char *_ptr; //文件输入的下一个位置
int _cnt; //当前缓冲区的相对位置
char *_base; //指基础位置(即是文件的其始位置)
int _flag; //文件标志
int _file; //文件的有效性验证
int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取
int _bufsiz; //缓冲区大小
char *_tmpfname; //临时文件名
};
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif
---------------------
来源:CSDN
原文:https://blog.csdn.net/jamenu/article/details/85066236
当然我们不必去深究其中的作用(会写标准库定义的人不知道比我们高到哪里去了)
但要稍微了解一下
下面介绍关于文件的函数
1.打开与关闭文件函数
fopen 打开文件函数 (成功打开后指向该流的文件指针就会被返回,失败返回NULL)
fclose 关闭文件函数
引用方法
fopen(文件名,使用文件方式); //文件名是一个字符串,使用方式需要加双引号,
为了使文件指针与文件建立联系我们要将函数返回的指针给我们的文件指针
像这样
FILE*fp;
fp = fopen("test.txt","r");//以读的方式打开默认路径下一个叫test的文本文件
可以说这样就将fp指向了test这个文件。
r(只读) 输入数据,打开一个已存在文本文件
w(只写) 输出数据,打开一个文本文件
a(追加) 向文本文件尾添加数据
rb(只读) 输入数据,打开一个2进制文件
wb(只写) 输出数据,打开一个2进制文件
r+(读写) 读写,文本文件
w+(读写) 读写,文本文件
a+(读写) 读写,文本文件
rb+(读写) 读写,二进制文件
wb+(读写) 读写,二进制文件
ab+(读写) 读写二进制文件
---------------------
来源:CSDN
原文:https://blog.csdn.net/jamenu/article/details/85066236
2.文件关闭
使用fclose()函数关闭文件,如果成功关闭文件,fclose()函数返回为零,如果关闭文件时发生错误,函数返回 EOFw函数原型为:
int fclose( FILE *fp );
3.文件写入
函数fputc()把参数c的字符值写入到fp所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回EOF。函数原型为:
int fputc( int c, FILE *fp );
函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。数原型为:
int fputs( const char *s, FILE *fp );
函数fprintf()把一个字符串写入到文件中,根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件.。 fprintf()的返回值是输出的字符数,发生错误时返回一个负值。函数原型为:
int fprintf( FILE *stream, const char *format, ... );
五.C语言链表
1.我们可以将一条链表想象成环环相扣的结点,就如平常所见到的锁链一样。链表内包含很多结点(当然也可以包含零个结点)。其中每个结点的数据空间一般会包含一个数据结构(用于存放各种类型的数据)以及一个指针,该指针一般称为next,用来指向下一个结点的位置。由于下一个结点也是链表类型,所以next的指针也要定义为链表类型。例如以下语句即定义了链表的结构类型。
typedef struct LinkList
{
int Element;
LinkList * next;
}LinkList;
2.链表初始化
在对链表进行操作之前,需要先新建一个链表。此处讲解一种常见的场景下新建链表:在任何输入都没有的情况下对链表进行初始化。
链表初始化的作用就是生成一个链表的头指针,以便后续的函数调用操作。在没有任何输入的情况下,我们首先需要定义一个头指针用来保存即将创建的链表。所以函数实现过程中需要在函数内定义并且申请一个结点的空间,并且在函数的结尾将这个结点作为新建链表的头指针返回给主调函数。本文给出的例程是生成一个头结点的指针,具体的代码实现如下:
linklist * List_init()
{
linklist *HeadNode= (linklist*)malloc(sizeof(linklist));
if(HeadNode == NULL)
{
printf("空间缓存不足");
return HeadNode;
}
HeadNode->Element= 0;
HeadNode->next= NULL;
returnHeadNode;
}
3.链表创建
创建链表需要将既定数据按照链表的结构进行存储,本文以一种最简单的方式来演示:使用数组对链表赋值。将原来在连续空间存放的数组数据,放置在不连续的链表空间中,使用指针进行链接。
链表创建的步骤一般使用给定的头指针以及需要初始化的数据序列作为输入参数,本文使用数组作为输入数据序列。在下面的例程中,先将首元结点使用数组第一个元素初始化,再在首元结点之后创建新的链表结点赋值数组内余下的数据。具体实现如下:
void CreatList(linklist *HeadNode,int *InData,int DataNum)
{
int i = 0;
linklist *CurrentNode = (linklist*) HeadNode;
for(i = 0;i<DataNum;i++)
{
CurrentNode->Element = InData[i];
if(i< DataNum-1)// 由于每次赋值后需要新建结点,为了保证没有多余的废结点
{
CurrentNode->next =(linklist *)malloc(sizeof(linklist));
CurrentNode= CurrentNode->next;
}
}
CurrentNode->next= NULL;
}
4.插入链表结点
链表创建完之后,下面我们将介绍如何向链表内插入结点。一般添加结点可以分为两类:一类是在链表尾部插入;另一类为在中间插入。
链表结尾添加结点的步骤就是新建一个链表结点,将其链接到当前链表尾指针。
在中间结点插入结点的步骤稍微复杂一些,其中也包含两种情况,分别是在指定结点前插入和指定结点后插入。其操作原理一样,本文只对指定位置后插入结点进行介绍。指定结点前插入结点留给大家尝试。
假设一个链表内存在几个几点A1,A2,A3,A4….,当根据要求需要在指定位置之后(比如A2结点)插入一个新结点时。首先我们需要新建立一个结点NodeToInsert,然后将新结点的next指向A3,并且将A2的next指针指向新建立的结点NodeToInsert,切记操作顺序不要改变。如果操作顺序变换一下,先将A2的next指向了新建立的结点,那么我们就丢失了A3的寻址方式。因此,在将A2的next指向其他任何地方之前,请务必将A3的地址存在NodeToInsert或者某个新建节点内。
插入结点的具体操作如下:
bool InsertList(linklist *HeadNode,int LocateIndex,int InData)
{
int i=1;// 由于起始结点HeadNode是头结点,所以计数从1开始
linklist *CurrentNode= (linklist *) HeadNode;
//将CurrentNode指向待插入位置的前一个结点(index -1)
while(CurrentNode&& i<LocateIndex-1)
{
CurrentNode= CurrentNode->next;
i++;
}
linklist *NodeToInsert=(linklist*)malloc(sizeof(linklist));
if(NodeToInsert == NULL)
{
printf("空间缓存不足");
return ERROR;
}
NodeToInsert->Element= InData;
NodeToInsert->next = CurrentNode->next;
CurrentNode->next = NodeToInsert;
return OK;
}
5.删除链表结点
对应于插入链表结点,链表的基本操作中同样也有删除链表结点。删除结点包括删除指定位置的结点和指定元素的结点。其基本原理都是先锁定待删除的结点的位置,然后将该结点的后置结点链接到前置结点的next指针处。这样中间这个结点即我们要删除的结点就从原来的链表中脱离开来。相对于原来的链表,即删除了该结点。
bool DeleteList(linklist * HeadNode,int index, int * DataToDel)
{
int i = 1;
linklist *CurrentNode = HeadNode;
linklist *NodeToDelete;
//将CurrentNode指向待删除位置的前一个结点(index -1)
while(CurrentNode&& i<index-1)
{
CurrentNode= CurrentNode->next;
i++;
}
NodeToDelete = CurrentNode->next;
*DataToDel =NodeToDelete->Element;
CurrentNode->next= NodeToDelete->next;
free(NodeToDelete);
return OK;
}
6.获取链表元素
接下来我们将“给定链表中的某一个位置,返回该位置的数据值”和“返回链表内某一个元素的位置”这两个问题放在一起介绍。
这两种情况的思路都是需要遍历链表。在给定元素值的情况下,定义一个元素序号随着遍历的过程累加,遍历的过程校验链表的结点是否与给定的元素匹配,如果匹配则返回元素位置的序号;在给定位置的情况下就更简单一些,元素序号累加到对应位置,返回对应结点的元素即可。
本文只列出给定元素值的例子:
int LocateElement(linklist * HeadNode,int DataToLocate)
{
int LocateIndex = 1;
linklist *CurrentNode= (linklist*) HeadNode;
while(CurrentNode)
{
if(CurrentNode->Element== DataToLocate)
{
returnLocateIndex; //找到位置返回
}
CurrentNode= CurrentNode->next;
LocateIndex++;
}
return -1; //如果没有这个值,返回-1
}