C语言笔记 08_函数指针&回调函数&字符串&结构体&位域

函数指针

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

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

函数指针能够像通常函数同样,用于调用函数、传递参数。编程

函数指针变量的声明:数组

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

如下实例声明了函数指针变量 p,指向函数 max:数据结构

#include <stdio.h>
 
int max(int x, int y)
{
    return x > y ? x : y;
}
 
int main(void)
{
    /* p 是函数指针 */
    int (* p)(int, int) = & max; // &能够省略
    int a, b, c, d;
 
    printf("请输入三个数字:");
    scanf("%d %d %d", & a, & b, & c);
 
    /* 与直接调用函数等价,d = max(max(a, b), c) */
    d = p(p(a, b), c); 
 
    printf("最大的数字是: %d\n", d);
 
    return 0;
}

编译执行,输出结果以下:dom

请输入三个数字:1 2 3
最大的数字是: 3编程语言

回调函数

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

函数指针变量能够做为某个函数的参数来使用的,回调函数就是一个经过函数指针调用的函数。函数

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

如下是来自知乎做者常溪玲的解说:code

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

实例

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

实例中咱们定义了回调函数 getNextRandomValue,它返回一个随机值,它做为一个函数指针传递给 populate_array 函数。

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

注意:size_t 是一种数据类型,近似于无符号整型,但容量范围通常大于 int 和 unsigned。这里使用 size_t 是为了保证 arraysize 变量可以有足够大的容量来储存可能大的数组个数值。

#include <stdlib.h>  
#include <stdio.h>
 
// 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
 
// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}
 
int main(void)
{
    int myarray[10];
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}

编译执行,输出结果以下:

16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709

字符串

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

下面的声明和初始化建立了一个 "Hello" 字符串。因为在数组的末尾存储了空字符,因此字符数组的大小比单词 "Hello" 的字符数多一个。

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

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

char greeting[] = "Hello";

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

img

其实,不须要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 '\0' 放在字符串的末尾。让咱们尝试输出上面的字符串:

#include <stdio.h>
 
int main ()
{
   char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
 
   printf("Greeting message: %s\n", greeting );
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Greeting message: Hello

操做字符串的函数

序号 函数 & 目的
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 的第一次出现的位置。

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

#include <stdio.h>
#include <string.h>
 
int main ()
{
   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 );
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

strcpy( str3, str1) : Hello
strcat( str1, str2): HelloWorld
strlen(str1) : 10

C 标准库中还有更多字符串相关的函数。

结构体

结构是 C 编程中一种用户自定义的可用的数据类型,它容许您存储不一样类型的数据项。结构用于表示一条记录。

定义结构

为了定义结构,您必须使用 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标签的结构体,另外声明了变量t一、t二、t3
struct SIMPLE t1, t2[20], *t3;
 
//也能够用typedef建立新类型
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//如今能够用Simple2做为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

在上面的声明中,第一个和第二声明被编译器看成两个彻底不一样的类型,即便他们的成员列表是同样的。

结构体的成员能够包含其余结构体,也能够包含指向本身结构体类型的指针,而一般这种指针的应用是为了实现一些更高级的数据结构如链表和树等。

//此结构体的声明包含了其余的结构体
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;
};

结构体变量的初始化

和其它类型变量同样,对结构体变量能够在定义时指定初始值。

#include <stdio.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
 
int main()
{
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}

执行输出结果为:

title : C 语言
author: RUNOOB
subject: 编程语言
book_id: 123456

访问结构成员

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

#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main( )
{
   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);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

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

结构做为函数参数

您能够把结构做为函数参数,传参方式与其余类型的变量或指针相似。

#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books book );
int main( )
{
   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 信息 */
   printBook( Book1 );
 
   /* 输出 Book2 信息 */
   printBook( Book2 );
 
   return 0;
}
void printBook( 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);
}

当上面的代码被编译和执行时,它会产生下列结果:

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

指向结构的指针

您能够定义指向结构的指针,方式与定义指向其余类型变量的指针类似,以下所示:

struct Books *struct_pointer;

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

struct_pointer = &Book1;

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

struct_pointer->title;

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

#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books *book );
int main( )
{
   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 的地址来输出 Book1 信息 */
   printBook( &Book1 );
 
   /* 经过传 Book2 的地址来输出 Book2 信息 */
   printBook( &Book2 );
 
   return 0;
}
void printBook( 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);
}

当上面的代码被编译和执行时,它会产生下列结果:

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

位域

有些信息在存储时,并不须要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 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 位。

1字节=1B=8个二进制位

让咱们再来看一个实例:

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..f四、一个 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;
    };

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

实例

若是程序的结构中包含多个开关量,只有 TRUE/FALSE 变量,以下:

struct
{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status;

这种结构须要 8 字节的内存空间,但在实际上,在每一个变量中,咱们只存储 0 或 1。上面的结构能够重写成:

struct
{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status;

如今,上面的结构中,status 变量将占用 4 个字节的内存空间,可是只有 2 位被用来存储值。若是您用了 32 个变量,每个变量宽度为 1 位,那么 status 结构将使用 4 个字节,但只要您再多用一个变量,若是使用了 33 个变量,那么它将分配内存的下一段来存储第 33 个变量,这个时候就开始使用 8 个字节。让咱们看看下面的实例来理解这个概念:

#include <stdio.h>
#include <string.h>
 
/* 定义简单的结构 */
struct
{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status1;
 
/* 定义位域结构 */
struct
{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status2;
 
int main( )
{
   printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
   printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Memory size occupied by status1 : 8
Memory size occupied by status2 : 4

位域声明

在结构内声明位域的形式以下:

struct
{
  type [member_name] : width ;
};

下面是有关位域中变量元素的描述:

元素 描述
type 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。
member_name 位域的名称。
width 位域中位的数量。宽度必须小于或等于指定类型的位宽度。

带有预约义宽度的变量被称为位域。位域能够存储多于 1 位的数,例如,须要一个变量来存储从 0 到 7 的值,您能够定义一个宽度为 3 位(2进制位)的位域,以下:

struct
{
  unsigned int age : 3;
} Age;

上面的结构定义指示 C 编译器,age 变量将只使用 3 位来存储这个值,若是您试图使用超过 3 位,则没法完成。让咱们来看下面的实例:

#include <stdio.h>
#include <string.h>
 
struct
{
  unsigned int age : 3;
} Age;
 
int main( )
{
   Age.age = 4;
   printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
   printf( "Age.age : %d\n", Age.age );
 
   Age.age = 7;
   printf( "Age.age : %d\n", Age.age );
 
   Age.age = 8; // 二进制表示为 1000 有四位,超出
   printf( "Age.age : %d\n", Age.age );
 
   return 0;
}

当上面的代码被编译时,它会带有警告,当上面的代码被执行时,它会产生下列结果:

Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0

位域的使用

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

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

位域容许用各类格式输出。

请看下面的实例:

#include <stdio.h>
int main(){
    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);    /* 用指针方式输出了这三个域的值 */
  return 0;
}

获得如下结果

1,7,15
0,3,15

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


参考自:https://www.runoob.com/cprogramming/c-tutorial.html

相关文章
相关标签/搜索