欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

C 语言学习 VI - 函数指针、回调函数、字符串和结构体

最编程 2024-03-25 21:27:41
...

函数指针

函数指针是指向函数的指针变量。

通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

函数指针可以像一般函数一样,用于调用函数、传递参数。

函数指针变量的声明:

typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型

实例

以下实例声明了函数指针变量 p,指向函数 max:

void test_function_pointer(){
    /* p 是函数指针 */
    int (* p)(int, int) = & max_num; // &可以省略
    int a, b, c, d;
    printf("please input three nums:");
    scanf("%d %d %d", & a, & b, & c);
    /* 与直接调用函数等价,d = max(max(a, b), c) */
    d = p(p(a, b), c);

    printf("max num is : %d\n", d);
}
int max_num(int num, int numT) {
    return num > numT ? num : numT;
}
please input three nums:3 69 12
3 69 12
max num is : 69

Process finished with exit code 0

回调函数

函数指针作为某个函数的参数

函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。

简单讲:回调函数是由别人的函数执行时调用你实现的函数。

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

实例

实例中 assignment_array函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。

实例中我们定义了回调函数 get_random_value,它返回一个随机值,它作为一个函数指针传递给 assignment_array函数。

assignment_array将调用 10 次回调函数,并将回调函数的返回值赋值给数组。

int get_random_value() {
    return rand();
}

void assignment_array(int *array, size_t arraySize, int(*get_dom_value(void))) {
    //从此处可以看出   函数指针作为某个函数的参数   时,方法名称不需一一对应
    for (size_t i = 0; i < arraySize; i++) {
        array[i] = get_dom_value();
    }

}
void test_assignment_array() {
    int myarray[10];
    assignment_array(myarray, 10, get_random_value);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
}
41 18467 6334 26500 19169 15724 11478 29358 26962 24464

Process finished with exit code 0

size_t 类型在C语言标准库函数原型使用的很多,数值范围一般是要大于int和unsigned.

但凡不涉及负值范围的表示size取值的,都可以用size_t;比如array[size_t]。

size_t 在stddef.h头文件中定义。

在其他常见的宏定义以及函数中常用到有:

1,sizeof运算符返回的结果是size_t类型;

2,void *malloc(size_t size)...

字符串

在 C 语言中,字符串实际上是使用 null 字符 '\0' 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。

下面的声明和初始化创建了一个 "Hello" 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 "Hello" 的字符数多一个。

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

依据数组初始化规则,您可以把上面的语句写成以下语句:

char greeting[] = "Hello";

以下是 C/C++ 中定义的字符串的内存表示:

image

C 中有大量操作字符串的函数:

序号 函数 & 目的
1 strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2 strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1); 返回字符串 s1 的长度。
4 strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

下面的实例使用了上述的一些函数:

void string_test() {
    char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
    printf("Greeting message: %s\n", greeting);
    
    char str1[12] = "Hello";
    char str2[12] = "World";
    char str3[12];
    int len;

    /* 复制 str1 到 str3 */
    strcpy(str3, str1);
    printf("strcpy( str3, str1) :  %s\n", str3);

    /* 连接 str1 和 str2 */
    strcat(str1, str2);
    printf("strcat( str1, str2):   %s\n", str1);

    /* 连接后,str1 的总长度 */
    len = strlen(str1);
    printf("strlen(str1) :  %d\n", len);
}
Greeting message: Hello
strcpy( str3, str1) :  Hello
strcat( str1, str2):   HelloWorld
strlen(str1) :  10

Process finished with exit code 0

您可以在 C 标准库中找到更多字符串相关的函数

结构体

C 数组允许定义可存储相同类型数据项的变量,结构C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:

  • Title

  • Author

  • Subject

  • Book ID

定义结构

为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:


struct tag { 
    member-list
    member-list 
    member-list  
    ...
} variable-list ;

tag 是结构体标签。

member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。

variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。下面是声明 Book 结构的方式:


struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。以下为实例:


//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;
 
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
 
//也可以用typedef创建新类型
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的。

结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。


//此结构体的声明包含了其他的结构体
struct COMPLEX
{
    char string[100];
    struct SIMPLE a;
};
 
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
    char string[100];
    struct NODE *next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:


struct B;    //对结构体B进行不完整声明
 
//结构体A中包含指向结构体B的指针
struct A
{
    struct B *partner;
    //other members;
};
 
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
    struct A *partner;
    //other members;
};

结构体变量的初始化

和其它类型变量一样,对结构体变量可以在定义时指定初始值。

//需要注意下 结构体的声明位置,和Java略有不同
struct Books
{
    char  title[50];
    char  author[50];
    char  subject[100];
    int   book_id;
} book = {"C ", "RUNOOB", "programa laguege", 123456};

void book_init() {
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}

访问结构成员

为了访问结构的成员,我们使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。您可以使用 struct 关键字来定义结构类型的变量。下面的实例演示了结构的用法:

void book_set(){
    struct Books Book1;        /* 声明 Book1,类型为 Books */
    struct Books Book2;        /* 声明 Book2,类型为 Books */

    /* Book1 详述 */
    strcpy( Book1.title, "C Programming");
    strcpy( Book1.author, "Nuha Ali");
    strcpy( Book1.subject, "C Programming Tutorial");
    Book1.book_id = 6495407;

    /* Book2 详述 */
    strcpy( Book2.title, "Telecom Billing");
    strcpy( Book2.author, "Zara Ali");
    strcpy( Book2.subject, "Telecom Billing Tutorial");
    Book2.book_id = 6495700;

    /* 输出 Book1 信息 */
    printf( "Book 1 title : %s\n", Book1.title);
    printf( "Book 1 author : %s\n", Book1.author);
    printf( "Book 1 subject : %s\n", Book1.subject);
    printf( "Book 1 book_id : %d\n", Book1.book_id);

    /* 输出 Book2 信息 */
    printf( "Book 2 title : %s\n", Book2.title);
    printf( "Book 2 author : %s\n", Book2.author);
    printf( "Book 2 subject : %s\n", Book2.subject);
    printf( "Book 2 book_id : %d\n", Book2.book_id);
}
Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

Process finished with exit code 0

结构作为函数参数

您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。您可以使用上面实例中的方式来访问结构变量:

void print_book( struct Books book )
{
    printf( "Book title : %s\n", book.title);
    printf( "Book author : %s\n", book.author);
    printf( "Book subject : %s\n", book.subject);
    printf( "Book book_id : %d\n", book.book_id);
}

将上方代码做修改。

void book_set(){
    struct Books Book1;        /* 声明 Book1,类型为 Books */
    struct Books Book2;        /* 声明 Book2,类型为 Books */

    /* Book1 详述 */
    strcpy( Book1.title, "C Programming");
    strcpy( Book1.author, "Nuha Ali");
    strcpy( Book1.subject, "C Programming Tutorial");
    Book1.book_id = 6495407;

    /* Book2 详述 */
    strcpy( Book2.title, "Telecom Billing");
    strcpy( Book2.author, "Zara Ali");
    strcpy( Book2.subject, "Telecom Billing Tutorial");
    Book2.book_id = 6495700;

    /* 输出 Book1 信息 */
    print_book(Book1);

    /* 输出 Book2 信息 */
    print_book(Book2);
}
Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700

Process finished with exit code 0

指向结构的指针

您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:

struct Books *struct_pointer;

现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_pointer = &Book1;

为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:

struct_pointer->title;

让我们使用结构指针来重写上面的实例,这将有助于您理解结构指针的概念:

我们来再次修改上述代码:

void print_books(struct Books *book) {
    printf("Book title : %s\n", book->title);
    printf("Book author : %s\n", book->author);
    printf("Book subject : %s\n", book->subject);
    printf("Book book_id : %d\n", book->book_id);
}

在C语言中,为了使用方便和使之直观,可以把 (*p).num 改用 p->num 来代替,它表示 p 所指向的结构体变量中的 num 成员,同样,(p).name 等价于 p->name。

也就是说以下三种形式等价:

a. 结构体变量.成员名
b. (*p).成员名
c. p-> 成员名

位域

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。

所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

典型的实例:

  • 用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
  • 读取外部文件格式——可以读取非标准的文件格式。例如:9 位的整数。

位域的定义和位域变量的说明

位域定义与结构定义相仿,其形式为:

struct 位域结构名 
{

 位域列表

};

其中位域列表的形式为:

类型说明符 位域名: 位域长度 

例如:


struct bs{
    int a:8;
    int b:2;
    int c:6;
}data;

说明 data 为 bs 变量,共占两个字节。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。

让我们再来看一个实例:


struct packed_struct {
  unsigned int f1:1;
  unsigned int f2:1;
  unsigned int f3:1;
  unsigned int f4:1;
  unsigned int type:4;
  unsigned int my_int:9;
} pack;

在这里,packed_struct 包含了 6 个成员:四个 1 位的标识符 f1..f4、一个 4 位的 type 和一个 9 位的 my_int。

对于位域的定义尚有以下几点说明:

  • 一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

    
    struct bs{
        unsigned a:4;
        unsigned  :4;    /* 空域 */
        unsigned b:4;    /* 从下一单元开始存放 */
        unsigned c:4
    }
    
    

    在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位。

  • 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。

  • 位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:

    
    struct k{
        int a:1;
        int  :2;    /* 该 2 位不能使用 */
        int b:3;
        int c:2;
    };
    
    

    从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。

    位域的使用

    位域的使用和结构成员的使用相同,其一般形式为:

    位域变量名.位域名
    位域变量名->位域名
    

位域允许用各种格式输出。

请看下面的实例:

void test_bit_field(){
    struct bs{
        unsigned a:1;
        unsigned b:3;
        unsigned c:4;
    } bit,*pbit;
    bit.a=1;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.b=7;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.c=15;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    printf("%d,%d,%d\n",bit.a,bit.b,bit.c);    /* 以整型量格式输出三个域的内容 */
    pbit=&bit;    /* 把位域变量 bit 的地址送给指针变量 pbit */
    pbit->a=0;    /* 用指针方式给位域 a 重新赋值,赋为 0 */
    pbit->b&=3;    /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
    pbit->c|=1;    /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
    printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);    /* 用指针方式输出了这三个域的值 */
}

上例程序中定义了位域结构 bs,三个位域为 a、b、c。说明了 bs 类型的变量 bit 和指向 bs 类型的指针变量 pbit。这表示位域也是可以使用指针的。

结构体中成员变量分配的空间是按照成员变量中占用空间最大的来作为分配单位,同样成员变量的存储空间也是不能跨分配单位的,如果当前的空间不足,则会存储到下一个分配单位中。

#include <stdio.h>

typedef struct
{
    unsigned char a;
    unsigned int  b;
    unsigned char c;
} debug_size1_t;
typedef struct
{
    unsigned char a;
    unsigned char b;
    unsigned int  c;
} debug_size2_t;

int main(void)
{
    printf("debug_size1_t size=%lu,debug_size2_t size=%lu\r\n", sizeof(debug_size1_t), sizeof(debug_size2_t));
    return 0;
}

编译执行输出结果:

debug_size1_t size=12,debug_size2_t size=8

结构体占用存储空间,以32位机为例

  • 1.debug_size1_t 存储空间分布为a(1byte)+空闲(3byte)+b(4byte)+c(1byte)+空闲(3byte)=12(byte)。

  • 1.debug_size2_t 存储空间分布为a(1byte)+b(1byte)+空闲(2byte)+c(4byte)=8(byte)。

结构体数组

一个结构体变量中可以存放一组数据(如一个学生的学号,姓名,成绩等数据)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与以前介绍过的数据值型数组不同之处在于每个数组元素都一个结构体类型的数据,它们分别包括各个成员(分量)项。

定义结构体数组

和定义结构体变量的方法相仿,只需说明其为数组即可。

struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
struct student stu[3];

以上定义了一个数组 stu,其元素为 struct student 类型数据,数组有 3 个元素。也可以直接定义一个结构体数组。如:

struct student
{
    int num;
    ....

}stu[3];

或

struct

{
    int num;
     ...
}stu[3];

结构体数组的初始化

与其它类型数组一样,对结构体数组可以初始化如:

struct student
{
    int mum;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}stu[3] = {{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
            {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},
            {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"}};

定义数组 stu 时,元素个数可以不指定,即写成以下形式:

stu[] = {{...},{...},{...}};

编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数。

当然,数组的初始化也可以用以下形式:

struct student
{
    int num;
    ...
};
struct student stu[] = {{...},{...},{...}};

即先声明结构体类型,然后定义数组为该结构体类型,在定义数组时初始化。

从以上可以看到,结构体数组初始化的一般形式是在定义数组的后面加上:

结构体数组应用举例

下面例子说明结构体数组的定义和引用。

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

struct person
{
    char name[20];
    int count;

}leader[3] = {{"Li", 0},
             {"Zhang", 0},
             {"Fun", 0}};

void main()
{
    int i, j;
    char leader_name[20];
    for(i = 1; i<= 10;i++)
    {
        scanf("%s", leader_name);
        for(j=0;j<3;j++)
            if(strcmp(leader_name, leader[j].name) == 0)
                leader[j].count ++;
    }
    printf("\n");
    for(i=0;i<3;i++)
        printf("%5s: %d\n", leader[i].name, leader[i].count);
    system("pause");
}

代码已上传github,点击此处即可到达