C语言入门(三)

原文地址:C 语言入门,感谢作者前辈

 

 

12. 数组

C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。

数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。

所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。

声明数组

在 C 中要声明一个数组,需要指定元素的类型和元素的数量,如下所示:

type arrayName [ arraySize ];

复制代码

这叫做一维数组。arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C 数据类型。例如,要声明一个类型为 double 的包含 10 个元素的数组 balance,声明语句如下:

double balance[10];

复制代码

初始化数组

void main(){
  double balance[10] = {1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0}
}

复制代码

大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目。

如果您省略掉了数组的大小,数组的大小则为初始化时元素的个数。因此,如果:

void main(){
  double balance[] = {1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0}
}

复制代码

您将创建一个数组,它与前一个实例中所创建的数组是完全相同的。下面是一个为数组中某个元素赋值的实例:

balance[1] = 50.5;

复制代码

 

C语言入门(三)_第1张图片

 

 

访问数组元素

//跟 Java 一样
double value = balance[1]

复制代码

例子:

void main() {
    //定义一个长度为 10 的整数数组
    int n[10];
    int i, j;

    //初始化数组元素
    for (i = 0; i < 10; i++) {
        n[i] = 2 * i;
    }

    //输出元素中的数据
    for (int k = 0; k < 10; ++k) {
        printf("Element[%d] = %d \n", k, n[k]);
    }

    //总的大小除以其中一个大小就得到了 数组长度
    printf("整数数组 n 的长度: %d \n", sizeof(n) / sizeof(n[0]));

    //输出元素中的数据
    for (int k = 0; k < sizeof(n) / sizeof(n[0]); ++k) {
        printf("Element[%d] = %d \n", k, n[k]);
    }
}

复制代码

输出:

Element[0] = 0 
Element[1] = 2 
Element[2] = 4 
Element[3] = 6 
Element[4] = 8 
Element[5] = 10 
Element[6] = 12 
Element[7] = 14 
Element[8] = 16 
Element[9] = 18 
整数数组 n 的长度: 10 
Element[0] = 0 
Element[1] = 2 
Element[2] = 4 
Element[3] = 6 
Element[4] = 8 
Element[5] = 10 
Element[6] = 12 
Element[7] = 14 
Element[8] = 16 
Element[9] = 18 


复制代码

C 中数组详解

在 C 中,数组是非常重要的,我们需要了解更多有关数组的细节。下面列出了 C 程序员必须清楚的一些与数组相关的重要概念:

概念 描述
多维数组 C 支持多维数组。多维数组最简单的形式是二维数组。
传递数组给函数 您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
从函数返回数组 C 允许从函数返回数组。
指向数组的指针 您可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。

13. 枚举

枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。

枚举语法定义格式为:

enum 枚举名 {枚举元素1,枚举元素2,……};

复制代码

接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:

#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7

复制代码

这个看起来代码量就比较多,接下来我们看看使用枚举的方式:

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

复制代码

这样看起来是不是更简洁了。

**注意:**第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

可以在定义枚举类型时改变枚举元素的值:

enum season {spring, summer=3, autumn, winter};

复制代码

没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

枚举变量的定义

前面我们只是声明了枚举类型,接下来我们看看如何定义枚举变量。

我们可以通过以下三种方式来定义枚举变量

1、先定义枚举类型,再定义枚举变量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

复制代码

2、定义枚举类型的同时定义枚举变量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

复制代码

3、省略枚举名称,直接定义枚举变量

enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

复制代码

例子:

void main() {
    //遍历一周
    for (day = MON; day <= SUN; day++) {
        printf("周: %d \n", day);
    }

    enum color { red=1, green, blue ,black};

    enum  color favorite_color;

//     ask user to choose color
    printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
    scanf("%d", &favorite_color);

//     输出结果
    switch (favorite_color)
    {
        case red:
            printf("你喜欢的颜色是红色");
            break;
        case green:
            printf("你喜欢的颜色是绿色");
            break;
        case blue:
            printf("你喜欢的颜色是蓝色");
            break;
        case black:
            printf("你喜欢的颜色是黑色");
            break;
        default:
            printf("你没有选择你喜欢的颜色");
    }


    //将整数转换为枚举
    enum day
    {
        saturday,
        sunday,
        monday,
        tuesday,
        wednesday,
        thursday,
        friday
    } ;
    int a = 1;
    enum day weekend;
    weekend = (enum day)a;
    printf("weekend:%d \n",weekend);
}

复制代码

输出:

周: 1 
周: 2 
周: 3 
周: 4 
周: 5 
周: 6 
周: 7 
请输入你喜欢的颜色: (1. red, 2. green, 3. blue): 1
你喜欢的颜色是红色weekend:1 

复制代码

14. 指针

学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。

正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:

void main(){
    int var1;
    char var2[10];
    //%p : 输出指针地址
    printf("var1 变量的地址:%p \n", &var1);
    printf("var2 变量的地址:%p \n", &var2);
}

复制代码

输出:

var1 变量的地址:0x7ffee7e976b8 
var2 变量的地址:0x7ffee7e976be 

复制代码

通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。

什么是指针?

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var-name

复制代码

在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int *i; //一个整型的指针	
double *d;//double 型指针
float *f;//浮点型指针
char *ch//字符型指针

复制代码

所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。

不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

如何使用指针?

使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 ***** 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:

例子:

    //如何使用指针
    int var = 66;//实际变量的声明
    int *ip;//指针变量的声明

    ip = &var; //指针变量中存储 var 的地址
    printf("var 的地址 : %p  \n", var);

    //在指针变量中存储的地址
    printf("ip 的地址:%p  \n", ip);

    //使用指针访问地址
    printf("ip 指针对应的地址:%p \n", *ip);

    //使用指针访问地址对应的值
    printf("ip 指针对应的地址:%d \n", *ip);

复制代码

输出:

var 的地址 : 0x42  
ip 的地址:0x7ffee96eb6b4  
ip 指针对应的地址:0x42 
ip 指针对应的地址:66 

复制代码

C 中的 NULL 指针

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为指针。

NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:

void main(){
    //赋值一个 NULL 指针
    int *ptr = NULL;
    printf("ptr 的地址是: %p \n", ptr);

    //检查一个空指针
    if (ptr) printf("如果 ptr 不是空指针,则执行"); else printf("如果 ptr 是空指针,则执行");
}

复制代码

输出:

ptr 的地址是: 0x0 ptr 是空指针

C 指针详解

在 C 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C 程序员必须清楚的一些与指针相关的重要概念:

概念 描述
指针的算术运算 可以对指针进行四种算术运算:++、--、+、-
指针数组 可以定义用来存储指针的数组。
指向指针的指针 C 允许指向指针的指针。
传递指针给函数 通过引用或地址传递参数,使传递的参数在调用函数中被改变。
从函数返回指针 C 允许函数返回指针到局部变量、静态变量和动态内存分配。

15. 函数指针与回调函数

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

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

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

函数指针变量的声明:

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

复制代码

例子:

 

C语言入门(三)_第2张图片

 

 

int max(int num1, int num2) {
    return (num1 > num2) ? num1 : num2;
}

void main() {

    //定义一个返回值为 int 类型,参数为 (int,int) 形式的函数指针
    int (*p)(int, int) = *max;
    int a, b, c, d;
    printf("请输入三个数字:\n");
    scanf("%d %d %d", &a, &b, &c);

    //与直接调用函数等价,d = max(max(a,b),c);
    d = p(p(a, b), c);
    printf("最大数字是: %d \n", d);

}

复制代码

回调函数

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

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

例子:

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

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

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

#include   
#include 
//回调函数
void populate_array(int *array, size_t arraySize, int(*getNextValue)(void)) {
    printf("array 地址:%p \n", array);
    for (size_t i = 0; i < arraySize; i++) {
        array[i] = getNextValue();
        printf(" array[%d] ,存储值:%d \n", i, array[i]);
    }
}

//获取一个随机数
int getNextRandomValue(void) {
    return rand();
}

void main() {

    //回调函数
    int array[10];
    printf("Int array 地址:%p \n", array);
    populate_array(array, sizeof(array)/sizeof(array[0]), getNextRandomValue);
    for (int i = 0; i < sizeof(array)/sizeof(array[0]); ++i) {
        printf(" array[%d] , 对应值为:%d \n", i, array[i]);
    }

}

复制代码

输出:

Int array 地址:0x7ffeebf1a650 
array 地址:0x7ffeebf1a650 
 array[0] ,存储值:16807 
 array[1] ,存储值:282475249 
 array[2] ,存储值:1622650073 
 array[3] ,存储值:984943658 
 array[4] ,存储值:1144108930 
 array[5] ,存储值:470211272 
 array[6] ,存储值:101027544 
 array[7] ,存储值:1457850878 
 array[8] ,存储值:1458777923 
 array[9] ,存储值:2007237709 
 array[0] , 对应值为:16807 
 array[1] , 对应值为:282475249 
 array[2] , 对应值为:1622650073 
 array[3] , 对应值为:984943658 
 array[4] , 对应值为:1144108930 
 array[5] , 对应值为:470211272 
 array[6] , 对应值为:101027544 
 array[7] , 对应值为:1457850878 
 array[8] , 对应值为:1458777923 
 array[9] , 对应值为:2007237709 

复制代码

16. 字符串

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

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

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

复制代码

也可以使用以下简写模式:

char ch[6] = "Hello"

复制代码

字符串在 C/C++ 中内存表示:

 

C语言入门(三)_第3张图片

 

 

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

void main(){
      //定义一个 char 数组
    char string[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
    //简写
    char string2[6] = "Hello";
    //%s:输出字符串
    printf("string message : %s\n", string);
}

复制代码

输出:

string message : Hello

复制代码

C 中对字符串操作的 API

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

例子:

void 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);

    //返回字符串的长度
    len = strlen(str1);
    printf("strlen(str1) :%d\n", len);
}

复制代码

输出:

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

复制代码

17. 结构体

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

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

  • Title
  • Author
  • Subject
  • Book ID

定义结构

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

struct name{
  member-list;
  member-list;
  ...
}name_tag,

复制代码

name 是结构的标签。

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

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

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

复制代码

注意:在定义结构体的时候name、member-list、name_tag 这 3 部分至少要出现 2 个。

结构体变量的初始化

和其它类型变量一样,在初始化的时候可以指定初始值。

//定义一个 Books 结构,类似于 Java 中的数据 bean
struct Books {
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
    double rmb;
} book = {"Java", "Android", "C 语言", 666, 55.5};


void main(){
      //打印 Books
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\nrmb: %f\n", book.title,
           book.author, book.subject, book.book_id, book.rmb);
}

复制代码

输出:

title : Java
author: Android
subject: C 语言
book_id: 666
rmb: 55.500000

复制代码

访问结构成员

struct Books2 {
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};
void main(){
      //访问 Books2 结构成员
    struct Books2 Books2A;//声明 Books2A 类型为 Books2
    struct Books2 Books2B;//声明 Books2B 类型为 Books2

    //Books2A 详述
    strcpy(Books2A.title, "C Plus");
    strcpy(Books2A.author, "Nuha Ali");
    strcpy(Books2A.subject, "C");
    Books2A.book_id = 666888;

    //Books2B 详述
    strcpy(Books2B.title, "C++ Plus");
    strcpy(Books2B.author, "DevYK");
    strcpy(Books2B.subject, "C++");
    Books2B.book_id = 666999;

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

    // 输出 Book2 信息
    printf("Book 2 title : %s\n", Books2B.title);
    printf("Book 2 author : %s\n", Books2B.author);
    printf("Book 2 subject : %s\n", Books2B.subject);
    printf("Book 2 book_id : %d\n", Books2B.book_id);
}

复制代码

输出:

Book 1 title : C Plus
Book 1 author : Nuha Ali
Book 1 subject : C
Book 1 book_id : 666888
Book 2 title : C++ Plus
Book 2 author : DevYK
Book 2 subject : C++
Book 2 book_id : 666999

复制代码

结构作为函数参数

//函数声明
void printBook(struct Books2 books2);

void main(){
      //访问 Books2 结构成员
    struct Books2 Books2A;//声明 Books2A 类型为 Books2
    struct Books2 Books2B;//声明 Books2B 类型为 Books2

    //Books2A 详述 ,将 CPlus copy 到 title 中
    strcpy(Books2A.title, "C Plus");
    strcpy(Books2A.author, "Nuha Ali");
    strcpy(Books2A.subject, "C");
    Books2A.book_id = 666888;

    //Books2B 详述
    strcpy(Books2B.title, "C++ Plus");
    strcpy(Books2B.author, "DevYK");
    strcpy(Books2B.subject, "C++");
    Books2B.book_id = 666999;

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

    // 输出 Book2 信息
    printf("Book 2 title : %s\n", Books2B.title);
    printf("Book 2 author : %s\n", Books2B.author);
    printf("Book 2 subject : %s\n", Books2B.subject);
    printf("Book 2 book_id : %d\n", Books2B.book_id);

    printf("\n\n\n");
    //结构作为函数参数
    printBook(Books2A);
    printBook(Books2B);
}


void printBook(struct Books2 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 1 title : C Plus
Book 1 author : Nuha Ali
Book 1 subject : C
Book 1 book_id : 666888
Book 2 title : C++ Plus
Book 2 author : DevYK
Book 2 subject : C++
Book 2 book_id : 666999



Book  title : C Plus
Book  author : Nuha Ali
Book  subject : C
Book  book_id : 666888
Book  title : C++ Plus
Book  author : DevYK
Book  subject : C++
Book  book_id : 666999




复制代码

指向结构的指针

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

struct Books *struct_pointer;

复制代码

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

struct_pointer = &Book1;

复制代码

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

struct_pointer->title;

复制代码

例子:

//定义指向结构的指针
void printBookZZ(struct Books2 *books2);

void main(){
      //访问 Books2 结构成员
    struct Books2 Books2A;//声明 Books2A 类型为 Books2
    struct Books2 Books2B;//声明 Books2B 类型为 Books2

    //Books2A 详述 ,将 CPlus copy 到 title 中
    strcpy(Books2A.title, "C Plus");
    strcpy(Books2A.author, "Nuha Ali");
    strcpy(Books2A.subject, "C");
    Books2A.book_id = 666888;

    //Books2B 详述
    strcpy(Books2B.title, "C++ Plus");
    strcpy(Books2B.author, "DevYK");
    strcpy(Books2B.subject, "C++");
    Books2B.book_id = 666999;
  
     //通过内存地址传递信息,为了查找结构变量的地址,请把 & 运算符放在结构名称的前面
     printBookZZ(&Books2A);
     printBookZZ(&Books2B);
} 

/**
 * 为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
 * @param book
 */
void printBookZZ(struct Books2 *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);
}

复制代码

位域

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

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

典型的实例:

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

位域ed 定义:

struct 位域结构名称{
  位域列表
};

复制代码

位域列表的形式为:

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

复制代码

例如:

struct bean {
  int a:8;
  int b:4;
  int c:4;
}data;

复制代码

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

注意:

  • 一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bean{
  unsigned a:4;
  unsigned  :4;//空域
  unsigned b:4;//从下一个单元开始存放
  unsigned c:4;
}

复制代码

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

  • 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。
  • 位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k{
 int a:1;
 int  :2;    /* 该 2 位不能使用 */
 int b:3;
 int c:2;
};

复制代码

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

位域的使用

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

位域变量名.位域名

位域变量名->位域名

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

例子:

void main(){
       //位域
     struct bs {
         unsigned int a:1;//占 位段a 1 位
         unsigned b:6;//占 位段b 3 位
         unsigned c:7;//占 位段c 4 位
     } bit, *pbit;
     // 给位域赋值(应注意赋值不能超过该位域的允许范围)
     bit.a = 1; //以二进制 1 表示 1 bit位
     bit.b = 50;//以二进制 110010 表示 6 bit位
     bit.c = 100;//以二进制 1100100 标志 7 bit位
     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 中原有值为 50,与 3 作按位与运算的结果为 2(110010&011=010,十进制值为 2)
     pbit->c|=1;     //使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 (1100100 | 0000001)= 1100101 = 101
     printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);     //用指针方式输出了这三个域的值
}

复制代码

输出:

1,50,100
0,2,101

复制代码

18. 共用体

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

定义共同体

为了定义共用体,您必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:

union [union tag]
{
member definition;
member definition;
...
member definition;
}[one or more union variables];

复制代码

union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:

union Data
{
int i;
float f;
char str[20];
}

复制代码

现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。

共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。下面的实例将显示上面的共用体占用的总内存大小:

union Data {
    int i;
    float f;
    char str[20];
};
void main(){
    union Data data;
    printf("Memory size occupied by data: %d\n", sizeof(data));
}

复制代码

输出:

Memory size occupied by data: 20

复制代码

访问共同体成员

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

union Data {
    int i;
    float f;
    char str[20];
};

void main() {
    //1. 访问共同体 no
    data.i = 10;
    data.f = 1314.520;
    strcpy(data.str,"C/C++");
    printf( "data.i : %d\n", data.i);
    printf( "data.f : %f\n", data.f);
    printf( "data.str : %s\n", data.str);

    printf("\n\n\n");
    //2. 访问共同体   yes
    data.i = 10;
    printf( "data.i : %d\n", data.i);
    data.f = 1314.520;
    printf( "data.f : %f\n", data.f);
    strcpy(data.str,"C/C++");
    printf( "data.str : %s\n", data.str);
 
}

复制代码

输出:

data.i : 725823299
data.f : 0.000000
data.str : C/C++



data.i : 10
data.f : 1314.520020
data.str : C/C++

复制代码

在这里,我们可以看到上面注释 1 共用体的 if 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。我们看注释 2 ,这次我们在同一时间只使用一个变量成员,所以都能完好输出。

19. 位域

参考 17.(位域的介绍)

20. typedef

C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE

typedef unsigned char BYTE;

复制代码

在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:

BYTE  b1, b2;

复制代码

按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但您也可以使用小写字母,如下:

typedef unsigned char byte;

复制代码

您也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:

typedef struct Books {
    char title[50];
    char author[50];
    char subject[50];
    int book_id;
} Book;

#define TRUE  1
#define FALSE  0

 void main(){
     Book book;
     strcpy( book.title, "C 教程");
     strcpy( book.author, "Runoob");
     strcpy( book.subject, "编程语言");
     book.book_id = 12345;
     printf( "书标题 : %s\n", book.title);
     printf( "书作者 : %s\n", book.author);
     printf( "书类目 : %s\n", book.subject);
     printf( "书 ID : %d\n", book.book_id);

     printf( "TRUE 的值: %d\n", TRUE);
     printf( "FALSE 的值: %d\n", FALSE);
 }

复制代码

输出:

书标题 : C 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
TRUE 的值: 1
FALSE 的值: 0

复制代码

typedef vs define

define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

  • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  • typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

例子可以参考上面是 #define 使用。

 

 

注:方便学习,侵删!

你可能感兴趣的:(C语言入门(三))