C 学习笔记 —— 结构(二)

2023-05-16

文章目录

    • 结构和指针
      • 结构体指针
      • 结构体中的指针
      • 结构做函数形参
      • 结构体作为函数返回值
      • 指针一些表达式
    • 把结构写入文件
    • 结构的存储结构(内存对齐)
        • 为何存在内存对齐
        • 结构体对齐规则四步走:
      • 参考资料

结构和指针

结构体指针

注意结构体还不是数组,所以他的变量名和数组不一样,并不表示首元素地址。而是单独一个类型,所以使用结构体指针要注意。

struct guy {
    char favfood[LEN];
    char job[LEN];
    float income;
};

int main(void)
{
    struct guy *him = {
            "grilled salmon",
            "personality coach",
            68112.00
    };
    him->income = 53.4;

    struct guy him2 = {
            "grilled salmon",
            "personality coach",
            68112.00
    };
    him2.income = 433.5;
    struct guy *him3 = &him2;
    him3->income = 43.5;
   
   	// 使用malloc给某个结构体分配内存
	struct guy *stu
	    = (struct guy *)malloc(sizeof(struct guy));
}

注意结构体一般比较大,所以对于大的结构体一般不采用在栈上分配内存,空间可能不够,而在堆上分配内存。

结构体中的指针

我们将结构体中的字符数组改成字符指针

struct funds1 {
    char   bank[20];
    char   save[20];
    }
struct funds2 {
    char   *bank;
    char   *save;
    }

对于funds1,结构体在创建对象的时候要分配40字节,
而对于funds2只需要16字节,因为每个指针占8字节。funds2不用为字符串分配空间,因为他存储的是地址,而字符串实际占用的空间是存储在别处的。所以这点在使用的时候就要注意。
看如下程序,就是malloc内存来存储字符串,然后用字符指针来指向这块内存。

struct namect {
    char * fname;  // using pointers
    char * lname;
    int letters;
};

void getinfo(struct namect *);        // allocates memory

int main(void)
{
    struct namect person;
    
    getinfo(&person);
}

void getinfo (struct namect * pst)
{
    char temp[SLEN];
    printf("Please enter your first name.\n");
    s_gets(temp, SLEN);
    // allocate memory to hold name
    pst->fname = (char *) malloc(strlen(temp) + 1);
    // copy name to allocated memory
    strcpy(pst->fname, temp);
}

结构做函数形参

struct funds {
    char   bank[FUNDLEN];
    double bankfund;
    char   save[FUNDLEN];
    double savefund;
};
//直接将结构体作为形参
double sum(struct funds moolah)
{
    return(moolah.bankfund + moolah.savefund);
}
//将结构体指针作为形参
double sum2(struct funds * money)
{
    return(money->bankfund + money->savefund);
}

int main(void)
{
    struct funds stan = {
        "Garlic-Melon Bank",
        4032.27,
        "Lucky's Savings and Loan",
        8543.94
    };
    sum(stan); //传递结构
    sum2(&stan) //传递结构的地址
}

因为sum函数接收一个结构体指针,但是用const修饰表示不能修改指针指向值的内容。
传递结构和传递结构地址进入函数顾名思义,就是传递结构的时候实际上传递的是结构的副本,程序使用和修改的都是副本。
而传递结构地址则使用的是原来的结构。

总结:
传递结构:优点,操作的是副本对原数据进行保护,缺点是传递结构浪费时间和存储空间。所以小型结构可以使用直接传结构。
传递地址:优点:操作快,且不需要占用额外空间。缺点:会误操作而修改原结构变量。所以对于大型结构使用地址传递,当然如果想防止误操作可以加const修饰。

绝大多数情况下我们都会使用指针作为参数传递结构。

结构体作为函数返回值

struct namect {
    char fname[NLEN];
    char lname[NLEN];
    int letters;
};

//声明一个函数
struct namect getinfo(void);

int main(void)
{
    struct namect person;
    
    person = getinfo();
}

//函数定义
struct namect getinfo(void)
{
    struct namect temp;
    printf("Please enter your first name.\n");
    s_gets(temp.fname, NLEN);
    printf("Please enter your last name.\n");
    s_gets(temp.lname, NLEN);
    
    return temp;
}

我们可以在函数内部创建一个结构体,然后作为返回值返回,并赋值给另一个结构体。

指针一些表达式

typedef struct {
  int a;
  short b[2];
} Ex2;
typedef struct EX{
  int a;
  char b[3];
  Ex2 c;
  struct EX *d;
} Ex;

Ex x = {10, "hi", {5, {-1, 252}}, 0}; //初始化 Ex
Ex *px = &x; 
int *pi;

px //指针变量保存一个指向x的地址。
px+1 //不合法,因为无法确定下一个位置,但是这里px如果是一个指向结构数组的元素,则他将指向下一个元素。
*px //px指向的整个结构
*px +1 //非法,没有定义结构和整型之间的运算
*(px+1) //如果x是一个数组元素则他表示他后面那个数组元素的结构
pi = px; //非法,类型不匹配
pi = (int*)px //可以,但是非常危险
pi = &px->a; //合法,->操作符优先级高于&
px->b[1] //访问数组b的第二个元素
px->c.a //合法
*px->c.b //b数组的第一个元素
*px->d //这里对一个NULL指针解引用是错误的,但是有些系统可能不会发现此错误

把结构写入文件

/* booksave.c -- saves structure contents in a file */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXTITL  40
#define MAXAUTL  40
#define MAXBKS   10             /* maximum number of books */
#define BOOKNAME "book.txt"
char * s_gets(char * st, int n);
struct book {                   /* set up book template    */
    char title[MAXTITL];
    char author[MAXAUTL];
    float value;
};

int main(void)
{
    struct book library[MAXBKS]; /* array of structures     */
    int count = 0; //记录输入的结构体数量
    int index, filecount; //filecount记录第几个结构体
    FILE * pbooks;
    int size = sizeof (struct book);
    
    //新建一个文件
    if ((pbooks = fopen(BOOKNAME, "a+b")) == NULL)
    {
        fputs("Can't open book.dat file\n",stderr);
        exit(1);
    }
    
    rewind(pbooks);            /* go to start of file     */
    //读文件内容
    while (count < MAXBKS &&  fread(&library[count], size,
                                    1, pbooks) == 1)
    {
        if (count == 0)
            puts("Current contents of book:");
        printf("%s by %s: $%.2f\n",library[count].title,
               library[count].author, library[count].value);
        count++;
    }
    filecount = count;
    if (count == MAXBKS)
    {
        fputs("The book.dat file is full.", stderr);
        exit(2);
    }
    
    puts("Please add new book titles.");
    puts("Press double [enter] to stop.");
    while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL
           && library[count].title[0] != '\0')
    {
        puts("Now enter the author.");
        s_gets(library[count].author, MAXAUTL);
        puts("Now enter the value.");
        scanf("%f", &library[count++].value);
        while (getchar() != '\n')
            continue;                /* clear input line  */
        if (count < MAXBKS)
            puts("Enter the next title.");
    }
    
    if (count > 0)
    {
        puts("Here is the list of your books:");
        for (index = 0; index < count; index++)
            printf("%s by %s: $%.2f\n",library[index].title,
                   library[index].author, library[index].value);
        //将结构体写入文件:定位到结构变量开始的位置,并把结构中所有字节都写入到文件
        //数据被分为count-file块,每块大小是一个结构体,写入到文件中 
        fwrite(&library[filecount], size, count - filecount,
               pbooks);
    }
    else
    	puts("No books? Too bad.\n");
    
    puts("Bye.\n");
    fclose(pbooks);
    
    return 0;
}

char * s_gets(char * st, int n)
{
    char * ret_val;
    char * find;
    
    ret_val = fgets(st, n, stdin);
    if (ret_val)
    {
        find = strchr(st, '\n');   // look for newline
        if (find)                  // if the address is not NULL,
            *find = '\0';          // place a null character there
        else
            while (getchar() != '\n')
                continue;          // dispose of rest of line
    }
    return ret_val;
}

结构的存储结构(内存对齐)

为何存在内存对齐

其实并没有一个官方的说法,但是从大部分的资料上来看,存在内存对齐是出于以下两个考量。

1.平台原因:
不是所有的硬件平台都能够访问任意地址上的任意数据,某些平台只能在某些地址取出某种特定类型的数据,否则会出现硬件异常。

2.性能原因:
数据结构(尤其是栈区)应该尽可能的在自然边界上对齐。(感觉咯不懂是吧,没关系,看图)。原因在于,为了访问未对齐的数据,处理器需要两次访问,二对齐的数据只需要一次访问。

结构体对齐规则四步走:

1.第一个结构体成员在结构体变量的偏移量为0的地址处
2.其他成员变量要对齐某个数字(对齐数)的整数倍的地址处(取系统平台比如64位=8bit和最大成员大小的较小值)
3.结构体总大小为最大对齐数(可能每个成员都有自己的对齐数,找个最大的)的整数倍。
4.特殊情况:
如果结构体嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍地址处,结构体整体大小就是所有最大对齐数(包含嵌套结构体)的整数倍数。

struct
{
    char a;
    double b;
    char c;
} var1;//24
struct
{
    char a;
    int b;
    char c;
} var2; //12
struct
{
    int a;
    char b;
    char c;
} var3; //8

int main(void)
{
    printf("%lu \n",sizeof(var1));
    printf("%lu \n",sizeof(var2));
    printf("%lu \n",sizeof(var3));
    return 0;
}

https://blog.csdn.net/luoheng1114/article/details/127106154

参考资料

结构的声明

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C 学习笔记 —— 结构(二) 的相关文章

随机推荐