喜迎
春节

结构体


1.结构体

(个人理解,结构体跟java中的类差不多)

可以理解为结构体是类的原形,毕竟c比java早诞生。

类似于类,但是不是类,因为c是面向过程编程,java是面向对象编程,两者之间是有差别的

可以在main函数中也可以在 main函数外(全局的)

1.1结构体的声明

结构体声明只是进行一个框架的描绘,并不会在内存中分配存储空间,直到真正定义一个结构体变量的时候。

案例:

1.2结构体变量

struct 结构体名称 结构体变量名

#include<stdio.h>

struct Book
{
    char title[128] ;
    char author[48] ;
    float price ;
    unsigned int date ;
    char publisher[40] ;
} book ; // 方式1 :也可以在这里定义book,是全局变量 


int main()
{
    struct Book book ; //方式二 : book为结构体的变量 
}

1.3访问结构体变量

#include<stdio.h>

struct Book
{
    char title[128] ;
    char author[48] ;
    float price ;
    unsigned int date ;
    char publisher[40] ;
} ; //也可以在这里定义book,是全局变量


int main()
{
    struct Book book ; //book为结构体的变量
    printf("书名:") ;
    scanf("%s",book.title) ;
    printf("作者: ") ;
    scanf("%s",book.author) ;
    printf("售价:") ;
    scanf("%f",&book.price) ;
    printf("日期:") ;
    scanf("%u",&book.date) ;
    printf("出版社: ") ;
    scanf("%s",book.publisher) ;

    printf("\n======录入完毕=======\n") ;


    printf("书名:%s\n",book.title) ;
    printf("作者:%s\n",book.author) ;
    printf("售价:%.2f\n",book.price) ;
    printf("日期:%u\n",book.date) ;
    printf("出版社:%s\n",book.publisher) ;
}

1.4初始化结构体变量

1.5初始化结构体的制定成员值

1.6 字节对齐

#include<stdio.h>

int main()
{
    struct A
    {
        char a ;
        int b ;
        char c ;
    } d = {'x',520,'0'} ;

    printf("size of a = %d\n",sizeof(d)) ;
 } 

char占一个字节,int四个字节,所以应该是6,但是答案并不是6! 正确答案为12

因为所有的数据都回进行内存对齐

可以观察到对齐后的大小是12 个字节。

结构体的大小是最大对齐数的整数倍,这里最大时4,所以没个都应该为4,一共12

==但是如果进行修改,将c的char值放在int的上面==

此时的结果为为8字节

    struct A
    {
        char a ;
        char c ;
        int b ;

    } d = {'x',520,'0'} ;

    printf("size of a = %d\n",sizeof(d)) ;
}

可以看出,这样的话,c就节省了4个内存空间

2.结构体嵌套

#include<stdio.h>

struct Date
{
    int year ;
    int month ;
    int day ;
} ;  

struct Book
{
    char title[128] ;
    char author[48] ;
    float price ;
    struct Date date ;
    char publisher[40] ;
} book = {
    "《c学习笔记》 ",
    "ljh",
    49.9,
    {2023,04,27},
    "清华大学出版社"  
} ;


int main()
{

    printf("书名:%s\n",book.title) ;
    printf("作者:%s\n",book.author) ;
    printf("售价:%.2f\n",book.price) ;
    printf("日期:%d-%d-%d\n",book.date.year,book.date.month,book.date.day) ;
    printf("出版社:%s\n",book.publisher) ;
}

3.结构体数组

方法1:在声明结构体的时候进行定义

方法2:

3.1初始化结构体数组

4.结构体指针

指向结构体的指针,称为结构体指针

结构体数组,数组名指向的第一个元素的地址,所以可以将数组赋值给结构体指针。

但是结构体数组和数组有差别,不一样的,要用取址符,取数组的地址再赋值给结构体指针。

4.1结构体指针访问结构体成员

int main()
{

    struct Book *pt ;
    pt = &book ;

    //方法1 
    printf("书名:%s\n",(*pt).title) ;
    printf("作者:%s\n",(*pt).author) ;
    printf("售价:%.2f\n",(*pt).price) ;
    printf("日期:%d-%d-%d\n",(*pt).date.year,(*pt).date.month,(*pt).date.day) ;
    printf("出版社:%s\n",(*pt).publisher) ;

    //方法2 推荐!!
    printf("书名:%s\n",pt->title) ;
    printf("作者:%s\n",pt->author) ;
    printf("售价:%.2f\n",pt->price) ;
    printf("日期:%d-%d-%d\n",pt->date.year,pt->date.month,pt->date.day) ;
    printf("出版社:%s\n",pt->publisher) ;

}

5.传递结构体变量

5.1两个结构体变量之间是否可以传递


int main()
{
    struct Test
    {
        int x ;
        int y ;
    }t1,t2;

    t1.x = 3 ;
    t1.y = 4 ;

    t2 = t1 ;

    printf("t2.x = %d, t2.y = %d\n",t2.x,t2.y ) ;

}

通过案例发现,两个结构体变量之间是可以传递的,可以对其进行赋值,前提是同一类型的才可以!

5.2 结构体作为函数类型,并返回值,并传递参数

一个demo

#include<stdio.h>

struct Date
{
    int year ;
    int month ;
    int day ;
} ;

struct Book
{
    char title[128] ;
    char author[48] ;
    float price ;
    struct Date date ;
    char publisher[40] ;
} ;

struct Book getInput(struct Book book); // 结构体类型函数的声明
void printBook(struct Book book) ; // 声明函数 


struct Book getInput(struct Book book)  //定义了结构体类型的函数,并传递结构体类型的形式参数和返回值
{
    printf("书名:") ;
    scanf("%s",book.title) ;
    printf("作者: ") ;
    scanf("%s",book.author) ;
    printf("售价:") ;
    scanf("%f",&book.price) ;
    printf("日期:") ;
    scanf("%d-%d-%d",&book.date.year,&book.date.month,&book.date.day) ;
    printf("出版社: ") ;
    scanf("%s",book.publisher) ;

    return book ;
}

void printBook(struct Book book)
{
    printf("书名:%s\n",book.title) ;
    printf("作者:%s\n",book.author) ;
    printf("售价:%.2f\n",book.price) ;
    printf("日期:%d-%d-%d\n",book.date.year,book.date.month,book.date.day) ;
    printf("出版社:%s\n",book.publisher) ;
}

int main()
{
    struct Book b1 ,b2 ;

    printf("请录入第一本书的信息。。。\n") ;
    b1 = getInput(b1) ;
    putchar('\n') ;
    printf("请录入第二本书的信息。。。\n") ;
    b2 = getInput(b2) ;

    printf("====录入完毕,打印验证====\n") ;

    printf("打印第一本书的信息\n") ;
    printBook(b1) ;

    putchar('\n') ;  // 换行
    printf("打印第二本书的信息\n") ; 
    printBook(b2) ;
}

传递结构体变量会增加计算机的运行负担,所以作为开发者,不会这样用,会使用指针来。

6.传递指向结构体变量的指针

#include<stdio.h>

struct Date
{
    int year ;
    int month ;
    int day ;
} ;

struct Book
{
    char title[128] ;
    char author[48] ;
    float price ;
    struct Date date ;
    char publisher[40] ;
} ;

void getInput(struct Book *book); // 传递指向结构体变量的指针
void printBook(struct Book *book) ; // 声明函数


void getInput(struct Book *book)  //传递指向结构体变量的指针,因为是直接再指针上修改的,所以不需要返回值
{
    printf("书名:") ;
    scanf("%s",book->title) ;
    printf("作者: ") ;
    scanf("%s",book->author) ;
    printf("售价:") ;
    scanf("%f",&book->price) ;
    printf("日期:") ;
    scanf("%d-%d-%d",&book->date.year,&book->date.month,&book->date.day) ;
    printf("出版社: ") ;
    scanf("%s",book->publisher) ;

}

void printBook(struct Book *book)  // 传递结构体变量指针
{
    printf("书名:%s\n",book->title) ;
    printf("作者:%s\n",book->author) ;
    printf("售价:%.2f\n",book->price) ;
    printf("日期:%d-%d-%d\n",book->date.year,book->date.month,book->date.day) ;
    printf("出版社:%s\n",book->publisher) ;
}

int main()
{
    struct Book b1 ,b2 ;

    printf("请录入第一本书的信息。。。\n") ;
    getInput(&b1) ; // 因为直接传的是地址,所以就不用赋值了,b1=.. 这一步就可以省略了 
    putchar('\n') ;
    printf("请录入第二本书的信息。。。\n") ;
    getInput(&b2) ;

    printf("====录入完毕,打印验证====\n") ;

    printf("打印第一本书的信息\n") ;
    printBook(&b1) ;

    putchar('\n') ;
    printf("打印第二本书的信息\n") ;
    printBook(&b2) ;
}

7.动态申请结构体

7.1使用malloc函数为结构体申请内存空间

其他不变,这里只写了修改了的部分同时需要多一个#include


int main()
{
    struct Book *b1 ,*b2 ;

    b1 = (struct Book *)malloc(sizeof(struct Book));
    b2 = (struct Book *)malloc(sizeof(struct Book)) ;
    if(b1 == NULL || b2 == NULL)
    {
        printf("内存分配失败!\n") ;
        exit(1) ; 
    }

    printf("请录入第一本书的信息。。。\n") ;
    getInput(b1) ; // 因为直接传的是地址,所以就不用赋值了,b1=.. 这一步就可以省略了 
    putchar('\n') ;
    printf("请录入第二本书的信息。。。\n") ;
    getInput(b2) ;

    printf("====录入完毕,打印验证====\n") ;

    printf("打印第一本书的信息\n") ;
    printBook(b1) ;

    putchar('\n') ;
    printf("打印第二本书的信息\n") ;
    printBook(b2) ;

    free(b1) ; //释放动态内存
    free(b2) ;
}

8.单链表

在数据结构与算法学习中,会更加详细的学习。

8.1单链表插入元素(头插法)

  • 1.先定义了结构体book,包含了信息域和该节点的指向
  • 2.定义了getInput函数,获取用户输入的图书信息
  • 3.定义了addbook函数,头插法,将用户输入的信息插入到链表中
  • 4.定义了printLibrary函数,用于遍历链表
  • 5.release函数,释放链表申请的内存空间
  • 主函数中初始化链表尾空链表,将链表头指向空
#include<stdio.h>
#include<stdlib.h>

struct Book
{
    char title[128] ;
    char author[40] ;
    struct Book *next ; //这里代表的是单链表的指向
} ;

void getInput(struct Book *book)
{
    printf("书名:\n");
    scanf("%s",book->title) ;
    printf("作者:\n") ;
    scanf("%s",book->author) ;
}

void addBook(struct Book **head)  // 这里的插入元素是在链表头插入,两层解引用,取next的地址的值,因为是头插法,可以将其理解为head。 补充:因为单链表的创建是创建了一个结构体的指针,所以下面的操作应该为*head,该函数在传递参数的时候只能传递**head,如果传递*head,下面在*head,就直接得到的是head的值,并不是指针类型的了。 
{
    struct Book *book , * tmp; // 结构体指针

    book = (struct Book *)malloc(sizeof(struct Book));


    getInput(book) ;// 填充信息域的内容。

    //将填好的结构体的信息域插进链表,链表头指向空值和不指向空值两种情况。
    if(*head != NULL)
    {
        tmp = *head ;// 先将原有结构体的指向tmp
        *head = book ;  //  指向这个新的结构体
        book ->next = tmp ;// 最后再将新的结构体book的指向指向之前的指向,因为是插在了他的前面。
    }
    else
    {
        *head = book  ; // next指向这个结构体book
        book -> next = NULL ; //  然后将新插入的结构体book的指向再改为null。
    }
}

void printLibray(struct Book *head)
{
    struct Book *book ;
    book = head ;
    int cnt = 1 ;

    while(book != NULL)
    {
        printf("book%d\n",cnt) ;//表示第几本书
        printf("书名:%s\n",book->title) ;
        printf("作者:%s\n",book->author) ;

        book = book->next ;
        cnt++ ;
    }
}

void release(struct Book *head) // 释放掉链表申请的内存空间
{
    while(head != NULL)
    {
        struct Book *next = head ->next ;
        free(head) ;
        head = next ;
     } 
}

int main()
{
    struct Book *head = NULL ; //结构体指针,刚开始指向null,代表空节点
    char ch ;

    while(1)
    {
        printf("请问是否需要录入书籍信息(y/n)");
        do
        {
            scanf("%c",&ch) ;
        }
        while(ch != 'y' && ch !='n') ;  //这里表示的是只有不是y或者n就一直输入,知道是y或者n的时候才结束跳出循环,这样的写法很不错!

        if(ch == 'y')
        {
            addBook(&head) ;
        }
        else
        {
            break ;
        }
    }

    printf("请问是否需要打印图书信息(y/n)") ;
    do
    {
        scanf("%c",&ch) ;
    }
    while(ch != 'y' && ch !='n') ;  //这里表示的是只有不是y或者n就一直输入,知道是y或者n的时候才结束跳出循环,这样的写法很不错!

    if(ch == 'y')
    {
        printLibray(head) ;
    }

    release(head) ;     

}

8.2尾插法

其他的代码没有改动,只需要改一下这里就可以了。

void add_end(struct Book **head)  // 这里的插入元素是在链表头插入,两层解引用,取next的地址的值,因为是头插法,可以将其理解为head
{
    struct Book *book , * tmp; // 结构体指针

    book = (struct Book *)malloc(sizeof(struct Book));


    getInput(book) ;// 填充信息域的内容。

    //将填好的结构体的信息域插进链表,链表头指向空值和不指向空值两种情况。
    if(*head != NULL)
    {
        tmp = *head ;
        //定位单链表的尾部位置
        while(tmp ->next != NULL)
        {
            tmp = tmp->next ;
        }
        //直到尾节点,会跳出上面的循环,插入数据
        tmp->next = book ;
        book->next = NULL ;
    }
    else
    {
        *head = book  ; // next指向这个结构体book
        book -> next = NULL ; //  然后将新插入的结构体book的指向再改为null。
    }
}

经过观察可以看出,头插和尾插,遍历的结果是不一样的。

但是上面的方法,效率较低,每次尾插都有遍历链表,可以设一个指针,指向单链表的末尾。

8.2.1指针的尾插法

void add_end(struct Book **head)  // 这里的插入元素是在链表头插入,两层解引用,取next的地址的值,因为是头插法,可以将其理解为head
{
    struct Book *book ; // 结构体指针
    static struct Book *tail ; // 用来记录链表的尾节点


    book = (struct Book *)malloc(sizeof(struct Book));


    getInput(book) ;// 填充信息域的内容。

    //将填好的结构体的信息域插进链表,链表头指向空值和不指向空值两种情况。
    if(*head != NULL)
    {
        tail->next = book ;
        book->next = NULL ; 
    }
    else
    {
        *head = book  ; // next指向这个结构体book
        book -> next = NULL ; //  然后将新插入的结构体book的指向再改为null。
    }
    tail = book ; //上面的if 和 else 不管执行谁,tail都是链表的尾节点指向book 
}

8.3搜索单链表

搜索单链表的方法

struct Book *searchBook(struct Book *head,char *target)
{
    struct Book *book ;

    book = head ;
    while(book != NULL)
    {
        if(!strcmp(book->title,target) || !strcmp(book->author,target)) //相等返回的是0,代表false,取反
        {
            break ;
        }
        book = book -> next ;
    }
    return book ;//返回的是找到的这个节点的指针
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Book
{
    char title[128] ;
    char author[40] ;
    struct Book *next ; //这里代表的是单链表的指向
} ;
void getInput(struct Book *book) ;
void add_end(struct Book **head) ;
void printLibray(struct Book *head);
struct Book *searchBook(struct Book *head,char *target) ;
void printBook(struct Book *book);
void release(struct Book *head) ;

void getInput(struct Book *book)
{
    printf("书名:\n");
    scanf("%s",book->title) ;
    printf("作者:\n") ;
    scanf("%s",book->author) ;
}

void add_end(struct Book **head)  // 这里的插入元素是在链表头插入,两层解引用,取next的地址的值,因为是头插法,可以将其理解为head
{
    struct Book *book ; // 结构体指针
    static struct Book *tail ; // 用来记录链表的尾节点


    book = (struct Book *)malloc(sizeof(struct Book));


    getInput(book) ;// 填充信息域的内容。

    //将填好的结构体的信息域插进链表,链表头指向空值和不指向空值两种情况。
    if(*head != NULL)
    {
        tail->next = book ;
        book->next = NULL ;
    }
    else
    {
        *head = book  ; // next指向这个结构体book
        book -> next = NULL ; //  然后将新插入的结构体book的指向再改为null。
    }
    tail = book ; //上面的if 和 else 不管执行谁,tail都是链表的尾节点指向book
}

void printLibray(struct Book *head)
{
    struct Book *book ;
    book = head ;
    int cnt = 1 ;

    while(book != NULL)
    {
        printf("book%d\n",cnt) ;//表示第几本书
        printf("书名:%s\n",book->title) ;
        printf("作者:%s\n",book->author) ;

        book = book->next ;
        cnt++ ;
    }
}

struct Book *searchBook(struct Book *head,char *target)
{
    struct Book *book ;

    book = head ;
    while(book != NULL)
    {
        if(!strcmp(book->title,target) || !strcmp(book->author,target)) //相等返回的是0,代表false,取反
        {
            break ;
        }
        book = book -> next ;
    }
    return book ;//返回的是找到的这个节点的指针
}

void printBook(struct Book *book)  // 输出搜索的结果的方法
{
    printf("书名: %s",book->title ) ;
    printf("作者: %s",book->author) ;
}

void release(struct Book *head) // 释放掉链表申请的内存空间
{
    while(head != NULL)
    {
        struct Book *next = head ->next ;
        free(head) ;
        head = next ;
    }
}

int main()
{
    struct Book *head = NULL ; //结构体指针,刚开始指向null,代表空节点
    char ch ,input[128]; // input是查找接受的字符数组
    struct Book *over; // 接收搜索链表返回的指针
    while(1)
    {
        printf("请问是否需要录入书籍信息(y/n)");
        do
        {
            scanf("%c",&ch) ;
        }
        while(ch != 'y' && ch !='n') ;  //这里表示的是只有不是y或者n就一直输入,知道是y或者n的时候才结束跳出循环,这样的写法很不错!

        if(ch == 'y')
        {
            add_end(&head) ;
        }
        else
        {
            break ;
        }
    }

    printf("请问是否需要打印图书信息(y/n)") ;
    do
    {
        scanf("%c",&ch) ;
    }
    while(ch != 'y' && ch !='n') ;  //这里表示的是只有不是y或者n就一直输入,知道是y或者n的时候才结束跳出循环,这样的写法很不错!

    if(ch == 'y')
    {
        printLibray(head) ;
    }

    printf("\n请输入书名或者作者:") ;
    scanf("%s",input) ;
    over = searchBook(head,input) ;
    if(over == NULL)
    {
        printf("很抱歉,没能找到!") ;
    }
    else
    {
        do
        {
            printf("已找到符合条件的图书。。\n") ;
            printBook(over) ; // 因为不止一本,所以要一直往下找 
        }while((over = searchBook(over->next,input)) != NULL) ; //没有到达链表尾部就一直找 
    }

    release(head) ;

}

8.4链表中插入元素

8.4.1插入元素的方法

void insertNode(struct Node **head,int value)  //如果插入第一个节点,我们要修改head指针的值,所以我们要传递指向node结构体的指针的指针,我们需要从内存空间的值修改,详情可以参考指针那一篇笔记
{
    struct Node *previous ; // 节点之前的指向
    struct Node *current ;  // 节点现在的指向
    struct Node *new ;  //可以参考图

    current = *head ;
    previous = NULL;

    while(current != NULL && current->value < value) // 从链表的头节点就开始查找,并且如果新传进来的值大于当前节点的值,就一直让他往后放,
//    当当前节点的值大于或者等于新传进来的值的时候,就会跳出循环了。
    {
        previous = current ;
        current = current->next ;
    }

    new = (struct Node *)malloc(sizeof(struct Node )) ;
    if(new == NULL)
    {
        printf("内存分配空间失败\n") ;
        exit(1) ;
    }
    new->value = value ;
    new->next = current ;
    if(previous == NULL)  //只有当传进来的head是空的单链表时,才会有这种情况,没有执行上面的while循环
    {
        *head = new ;
    }
    else
    {
        previous->next = new ; //指向新插入的节点,看图会更加明白
    }
}

解释该方法:

  • 该方法insertNode,用于向链表插入一个新节点,并且是按数值从小到大排序的,插入节点时,需要传递链表头节点的地址,以及需要插入的值

  • 在函数中传入的head是一个指向struct Node 的指针变量,因为我们想要在函数内部修改它指向的内存空间的值,所以需要使用指向指针的指针,即两个*号,

  • 在函数中,新节点插入到链表头部时,实际上是修改了head所指内存空间的值,将其指向新节点,因此,为了能够实现这样的操作,需要将head的地址作为参数传递给函数,也就是指针struct Node ** 类型的变量,这样,我们就可以通过修改指向指针所指的内存空间的值来改变head的指向了。

8.4.2完整代码

#include<stdio.h>
#include<stdlib.h>

struct Node
{
    int value ;
    struct Node *next ;
} ;

void insertNode(struct Node **head,int value)  //如果插入第一个节点,我们要修改head指针的值,所以我们要传递指向node结构体的指针的指针,我们需要从内存空间的值修改,详情可以参考指针那一篇笔记
{
    struct Node *previous ; // 节点之前的指向
    struct Node *current ;  // 节点现在的指向
    struct Node *new ;  //可以参考图

    current = *head ;
    previous = NULL;

    while(current != NULL && current->value < value) // 从链表的头节点就开始查找,并且如果新传进来的值大于当前节点的值,就一直让他往后放,
//    当当前节点的值大于或者等于新传进来的值的时候,就会跳出循环了。
    {
        previous = current ;
        current = current->next ;
    }

    new = (struct Node *)malloc(sizeof(struct Node )) ;
    if(new == NULL)
    {
        printf("内存分配空间失败\n") ;
        exit(1) ;
    }
    new->value = value ;
    new->next = current ;
    if(previous == NULL)  //只有当传进来的head是空的单链表时,才会有这种情况,没有执行上面的while循环
    {
        *head = new ;
    }
    else
    {
        previous->next = new ; //指向新插入的节点,看图会更加明白
    }
}
void printNode(struct Node *head)
{
    struct Node *current ;

    current = head ;
    while(current != NULL)
    {
        printf("%d ",current->value) ;
        current = current->next ;
    }
    putchar('\n') ;// 换行
}
int main()
{
    struct Node *head = NULL ;
    int input ;

    while(1)
    {
        printf("请输入一个整数:(输入-1结束)") ;
        scanf("%d",&input) ;
        if(input == -1) break ;
        insertNode(&head,input) ;
        printNode(head) ;
    }
}

8.5删除链表的节点

8.5.1删除链表的方法


void deleteNode(struct Node **head , int value)
{
    struct Node *previous ;
    struct Node *current ;

    current = *head ;
    previous = NULL ;

    while(current != NULL && current->value != value)
    {
        previous = current ;
        current = current->next ;
    }

    if(current == NULL)
    {
        printf("找不到匹配的节点\n") ;
        return ; // 整个函数结束 
    }
    else
    {
        if(previous == NULL)  // 特殊情况,该节点在第一个节点的位置,就没有指向上面的while循环 
        {
            *head = current->next ; //删除当前节点,在第一个节点的位置,就是让头节点head指向,该节点的下一个位置
        }
        else
        {
            previous->next = current->next ; 
        }

        free(current) ; // 删除了该节点,就释放其空间。    
    } 
 } 
 

8.5.2完整代码,参考8.4.2

10.基础typedef

相比宏定义的直接替换,typedef是对类型的封装

案例:

#include<stdio.h>
#define integer int // 意思是用integer 代替 int

typedef int integer ;

int main()
{
    integer a ;
    int b ;

    a = 520 ;
    b = a ;

    printf("a = %d\n",a ) ;
    printf("b = %d\n",b) ;
    printf("size of a = %d\n",sizeof(a)) ; 
}

可能会有疑惑,这跟宏定义不是差不多嘛,对的,可以用宏定义来代替,但是用宏定义和用typedef是有差别的

看下方案例

#include<stdio.h>

//#define integer int  // 这个不会报错 ,但是输出的结果不对 
typedef int integer ; //会报错 

int main()
{
    unsigned integer a ; // 无符号整形

    a = -1 ; 
    printf("a = %u\n",a) ;  // 输出的是-1的补码他的类型是无符号整形    ,所以他的补码被当做无符号的正数输出 

}
#include<stdio.h>

typedef int INTEGER , *PTRINT;  // 可以这样写,一次封装多个 
//typedef int *PTRINT ;

//#define PTRINT int*  // 如果用宏定义,就会警告报错,因为,宏定义替换的,只是将b替换为int类型的指针,c并不是指针 

int main()
{
    INTEGER a = 520 ;
    PTRINT b, c ;

    b = &a ;
    c = b ;

    printf("addr of a = %p\n",c) ;
}

10.1typedef和struct

没有typedef:

#include<stdio.h>
#include<stdlib.h>

struct Date
{
    int year ;
    int month ;
    int day ;
} ;

int main()
{
    struct Date *date ;

    date = (struct Date *)malloc(sizeof(struct Date)) ;
    if(date == NULL)
    {
        printf("分配内存失败") ;
        exit(1) ;
    }

    date->year = 2023 ;
    date->month = 5 ;
    date->day = 1 ;

    printf("%d-%d-%d",date->year , date->month , date->day) ; 
}

有了typedef:

 #include<stdio.h>
#include<stdlib.h>

typedef struct Date
{
    int year ;
    int month ;
    int day ;
} DATE, *PDATE;  // 需要用到该结构体的,和该结构的指针,统一格式,加一个*P 


int main()
{
    //struct Date *date ;
    PDATE date ;

    date = (PDATE)malloc(sizeof(DATE)) ;
    ......
        ....
        ....
    ......以下内容同上,省略
}

可以用typedef封装结构体。

注意:封装结构体时,一般在下面写两个 一个是结构体的大写名字,一个是加了P+结构体名字,代表该结构体的指针,后面在使用的时候就不要写`struct Date 或者 struct Date 了, 只需要写DATE , PDATE`, 这是在封装结构体时,起的别名。

11.进阶typedef

11.1数组指针和typedef

#include <stdio.h>

typedef int (*PTR_TO_ARRAY)[3] ;

int main()
{
    int array[3] = {1,2,3} ;
    PTR_TO_ARRAY ptr_to_array = &array ;

    int i ;
    for(i = 0 ; i < 3 ; i++)
    {
        printf("%d\n",(*ptr_to_array)[i]) ;
    }
}

11.2函数指针和typedef

#include<stdio.h>

typedef int (*PTR_TO_FUN)(void) ;

int fun()
{
    return 520 ;
}

int main()
{
    PTR_TO_FUN ptr_to_fun = &fun ;

    printf("%d\n",(*ptr_to_fun)()) ; //这里注意要在后面加上一个括号(),表示指针解引用之后是一个函数的调用 

}

(*)ptr_to_fun()是指调用了该函数,后面的那个括号是用来传递参数的,如果不用传递参数,就只写一个()就好了,有参数的话,把参数写进去。

11.3指针数组和typedef

#include<stdio.h>

typedef int *(*PTR_TO_FUN)(int) ;

int *fun1(int num)
{
    return &num; 
} 
int *fun2(int num)
{
    return &num;
}
int *fun3(int c )
{
    return &c ; 
}
int main()
{
    PTR_TO_FUN array[3] = {&fun1,&fun2,&fun3} ; //指针数组,存放指向每个函数的指针

    for(int i = 0 ; i < 3 ; i++)
    {
        printf("add of num :%p\n",(*array[i])(i)) ;// array的元素是一个地址,指针数组,所以解引用,解引用之后,是这个函数的地址,调用函数后面要加小括号 ,相当于调用该函数,如果有形式参数,需要在小括号里写上 
     } 

}

上面这样写是不合法的,会有报错,只是用来举一个案例。

12.共用体

共用体的所有成员共享同一个内存地址

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

union Test
{
    int i ;
    double pi ;
    char str[6] ;
} ;

int main()
{
    union Test test ;// 共用体的变量名
    test.i = 520 ;
    test.pi = 3.14 ;
    strcpy(test.str,"FishC") ;

    printf("addr of test.i: %p\n",&test.i) ;
    printf("addr of test.pi: %p\n",&test.pi) ;
    printf("addr of test.str: %p\n",&test.str) ;

    printf("test.i: %d\n",test.i) ;
    printf("test.pi: %.2f\n",test.pi) ;
    printf("test.str: %s\n",test.str) ;
    //如果想要同时打印出共用体的值,只有最后一个打印的是正确的,因为会被覆盖,只有最后一个赋值的是正确的

    printf("size of test %d",sizeof(test)) ; // 与内存对齐有关系。 
}

12.1定义共用体类型变量

12.2初始化共用体

13.枚举类型

如果一个变量只有几种可能的值,那么就可以将其定义为枚举类型

13.1声明枚举变量

13.2定义枚举变量

#include<stdio.h>
#include<time.h> //加载时间库,获取周几 

int main()
{
    enum Week {sun,mon,tue,wed,thu,fri,sat} ;  //声明枚举变量
    enum Week today ;          //定义枚举变量

    struct tm *p ; // time函数返回的值为指针
    time_t t ;  //  time_t 类型变量的指针

    time(&t) ;  // 需要传入time_t类型的变量,获取当前系统的秒数
    p = localtime(&t) ; //localtime的返回值是tm类型的指针

    today = p->tm_wday ; //tm_day返回值是0-6,从周一开始,和java的calendar函数一样。
    //today得到tm_wday的返回值之后去枚举变量中找从0-6开始的序号,一一对应的,然后得到一个值
    // 再去传递给switch函数,就得到了正确的结果了。 

    switch(today)
    {
        case mon :
        case tue :
        case wed :
        case thu :
        case fri :
            printf("学习\n") ;
            break ;
        case sat :
        case sun:
            printf("周末\n") ;
            break ;
        default:
            printf("Error") ; 

    }

}

案例:

有指定值,指定值后面的值就会加一,依次累加,如果指定值前面还有没有指定的值,那么那些值将从0开始

#include<stdio.h>

int main()
{
    enum Color {red = 10 , green , blue} ; // 有指定值,指定值后面的值就会加一,如果指定值前面还有没有指定的值,那么那些值将从0开始 
    enum Color rgb ;

    for(rgb = red ; rgb <= blue ;rgb++)
    {
        printf("rgb is %d\n",rgb) ;
     } 

 } 

文章作者: ljhaa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ljhaa !
评 论
 上一篇
文件
文件
0.什么是文件 1.文本文件和二进制文件c语言中主要有两种文件,文本文件和二进制文件。 2.打开和关闭文件 2.1打开文件2.1.1fopen()函数FILE *fp; // 定义文件指针 fp = fopen("filename", "
2023-05-02
下一篇 
宏定义
宏定义
1.不带参数的宏定义#define PI 3.14 将文中的所有的PI替换为3.14 #define PI 3.1415926 float radius = 2.0; float area = PI * radius * radius;
2023-05-02
  目录