精华内容
下载资源
问答
  • Part 4 接上文....对于GObject而言,有了实例结构,有了结构,自然要有如何初始化的函数,毕竟这是C语言吗,没有那么方便的事儿。相应的,对应于jc_boy,要定义jc_boy_initjc_boy_class_init两个

    Part 4

       接上文.
    前面定义了几个结构,我们可以把那些东西放在一个jc_boy.h文件中,下面开始介绍如何去实现这个BOY类,也就是要放在jc_boy.c中的部分。
    对于GObject而言,有了实例结构,有了类结构,自然要有如何初始化的函数,毕竟这是C语言吗,没有那么方便的事儿。

    相应的,对应于jc_boy,要定义jc_boy_initjc_boy_class_init两个函数,当然他们分别对应于实例结构和类结构的初始化。

    static void
    jc_boy_init (JcBoy 
    *self)
    {
         JcBoyPrivate 
    *priv=NULL;
         pri
    =self->private = JC_BOY_GET_PRIVATE (self); 
         priv
    ->name=g_strdup("no-name");
         priv
    ->hobby=g_strdup("nothing");
         priv
    ->age=0;
    }

     

    static用于声明作用域,对于由多个文件构成的工程而言,很有必要。

           看到JC_BOY_GET_PRIVATE了吧,就是前面的那个宏定义,它调用的是G_TYPE_INSTANCE_GET_PRIVATE,一看全都是大写和下划线,
    直觉告诉我们这还是宏定义,没错,它调用的是g_type_instance_get_private。 有时感觉这么折腾几下会很麻烦,但GObject就是建议大家
    这么用,如果你不想使用那么一堆宏定义,那么直接使用g_type_instance_get_private之类的函数也是可以的。
    JC_BOY_GET_PRIVATE获取了一个指向实例私有结构体的一个指针,接下来就是对其初始化了,注意g_strdup在堆上分配了一个字符串,
    在后面介绍的类的析构的时候,我们应该将其free一下。 
    P.S.你可以把这里的init理解为construtor,也可以不这样理解,因为GObject的类中提供了construtor函数,你可以在你定义的类中重写它。
    但是一般简单的应用直接在初始化函数中处理了。
    下面再看下类结构的初始化
    static void
    jc_boy_class_init (JcBoyClass 
    *klass)

    {
        g_type_class_add_private (klass, 
    sizeof (JcBoyPrivate));
        klass
    ->play=play;
    }
    首先将实例中的私有结构在类结构中声明下,这里用的是g_type_class_add_private.这里就没有使用宏定义。
    然后将类结构中的那个函数指向一个函数,
    void play()
    {
        g_print(“the boy is playing football !/n”);
    }
    但有结构的初始化函数,还需要向GObject系统注册这个类【就是注册有这么一个类型】。
    类的注册其实是比较麻烦的,这要用到g_type_register_***,可以注册成fundamental的,static的,dynamic的,
    一般的应用基本用的都是static的,也是本文要讲述的【主要是自己还没怎么研究dynamic的缘故……XD】
    以g_type_register_static为例
    GType   g_type_register_static(GType parent_type,
                                                          const gchar *type_name,
                                                          const GTypeInfo *info,
                                                          GTypeFlags flags);
    看英文大概能明白每个参数是干嘛的,这里面比较麻烦的是GTypeInfo,还是简单看下
    typedef struct {
      
    /* interface types, classed types, instantiated types */
      guint16                class_size;
      
      GBaseInitFunc          base_init;
      GBaseFinalizeFunc      base_finalize;
      
      
    /* interface types, classed types, instantiated types */
      GClassInitFunc         class_init;
      GClassFinalizeFunc     class_finalize;
      gconstpointer          class_data;
      
      
    /* instantiated types */
      guint16                instance_size;
      guint16                n_preallocs;
      GInstanceInitFunc      instance_init;
      
      
    /* value handling */
      
    const GTypeValueTable    *value_table;
    } GTypeInfo;

    不要多看,多看会更头晕,但这并不影响我们使用GObject来构建对象

    【什么?你非要弄明白?那别看本文了,本文标题写着入门俩字呢!。。。。。。其实主要原因是自己也没有完全研究透。。。。。。XD】

     但是要明白这里向系统声明了init ,finalize等等函式。

     为了简化这些步骤,GObject里面为我们提供了一些简化宏定义。

     这里介绍一个比较简单的 G_DEFINE_TYPE (JcBoy  jc_boy  G_TYPE_OBJECT) ;

    前两个函式主要用于宏展开,最后一个用于表明父类的类型【对于继承下来的类,最后一个参数可别用错】

     还是展开看一下,其实还是比较麻烦的,具体我就不多说了。
    #define G_DEFINE_TYPE(TN, t_n, T_P) G_DEFINE_TYPE_EXTENDED (TN, t_n, T_P, 0, {})

    #define G_DEFINE_TYPE_EXTENDED(TN, t_n, T_P, _f_, _C_)      _G_DEFINE_TYPE_EXTENDED_BEGIN (TN, t_n, T_P, _f_) {_C_;} _G_DEFINE_TYPE_EXTENDED_END()

    #define _G_DEFINE_TYPE_EXTENDED_BEGIN(TypeName, type_name, TYPE_PARENT, flags) 

    static void     type_name##_init              (TypeName        *self); /
    static void     type_name##_class_init        (TypeName##Class *klass); /
    static gpointer type_name##_parent_class = NULL; /
    static void     type_name##_class_intern_init (gpointer klass) /
    { /
      type_name##_parent_class 
    = g_type_class_peek_parent (klass); /
      type_name##_class_init ((TypeName##Class
    *) klass); /
    } /
    /
    gulong/
    type_name##_get_type (
    void) /
    { /
      
    static volatile gsize g_define_type_id__volatile = 0; /
      
    if (g_once_init_enter (&g_define_type_id__volatile))  /
        { /
          gulongg_define_type_id 
    = /
            g_type_register_static_simple (TYPE_PARENT, /
                                           g_intern_static_string (#TypeName), /
                                           
    sizeof (TypeName##Class), /
                                           (GClassInitFunc) type_name##_class_intern_init, /
                                           
    sizeof (TypeName), /
                                           (GInstanceInitFunc) type_name##_init, /
                                           (GTypeFlags) flags); /
          { 
    /* custom code follows */
    #define _G_DEFINE_TYPE_EXTENDED_END()   /
            
    /* following custom code */ /
          }                 /
          g_once_init_leave (
    &g_define_type_id__volatile, g_define_type_id); /
        }                   /
      
    return g_define_type_id__volatile;    /
    /* closes type_name##_get_type() */
    另外,为了方便设置类中的成员还要设置几个可以由外部调用的的函数。
    void jc_boy_change_hobby(JcBoy *self,gchar *hobby)
    {
        JcBoyPrivate *priv;
        self->priv = priv = JC_BOY_GET_PRIVATE(self);
        g_free (priv->hobby);
        priv->hobby=g_strdup("playing basketball");
        g_print("recently the boy is interested in %s/n",hobby);
    }


    展开全文
  • c语言编写单片机技巧

    2009-04-19 12:15:17
    1. C语言和汇编语言在开发单片机时各有哪些优缺点? 答:汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言...
  • 你必须知道的495个C语言问题

    千次下载 热门讨论 2015-05-08 11:09:25
    1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 1.4 新的64位机上的64位类型...
  • C语言没有类这种“类型”的,但是C语言就不能模拟“面向对象”编程了吗? 不,只要你设计得好,C语言也可以模拟面向对象编程。这一课我们学到的关于struct(结构体)的知识就可以使你有能力用C语言实现面向对象。 ...

     

    作者 谢恩铭,公众号「程序员联盟」(微信号:coderhub)。
    转载请注明出处。
    原文:https://www.jianshu.com/p/39b41aa5cca7

    《C语言探索之旅》全系列

    内容简介


    1. 前言
    2. 定义一个 struct
    3. 结构体的使用
    4. 结构体指针
    5. union
    6. enum
    7. 总结
    8. 第二部分第七课预告

    1. 前言


    上一课是 C语言探索之旅 | 第二部分第五课:预处理 ,应该是比较轻松的。

    这一课将会非常令人激动也很有意思,不过有些难度。

    众所周知,C语言是面向过程的编程语言,与 Java,C++,等面向对象的编程语言有所不同。

    在面向对象的编程语言中,有(class)的概念。

    C语言是没有类这种“类型”的,但是 C语言就不能“模拟”面向对象编程了吗?

    不,只要你设计得好,C语言也可以模拟面向对象编程。

    这一课我们要学习的 struct(结构体)的知识就可以使你有能力用 C语言实现“面向对象”。

    前面我们学习了指针,数组,字符串和预处理,掌握这些知识你的 C语言水平已经还不错啦。但是我们岂能就此止步,必须 Bigger than bigger~

    除了使用 C语言已经定义的变量类型,我们还可以做一些更厉害的事情:创建你自己的变量类型

    我们可以将其称为“自定义的变量类型”,我们来看三种:struct,union 和 enum。

    因为当你需要编写比较复杂的程序时,你会发现创建自定义的变量类型是很重要的。

    幸好,这学起来其实也不是特别难。但是大家需要专心学习这一课,因为从下一课开始,我们会一直用到 struct 了。

    2. 定义一个 struct


    什么是 struct 呢?

    struct 是 structure(表示“结构”)的缩写,所以 struct 的专业术语是“结构体”。

    定义:struct 就是一系列变量的集合,但是这些变量可以是不同类型的。

    这个定义是不是唤起了大家对我们的老朋友数组的怀念啊?数组里面的每个成员都必须是同一个类型的,相比之下 struct 更灵活。

    一般来说,我们习惯把 struct 定义在 .h 头文件中,也就是和预处理命令以及函数原型那群“家伙”在一起。

    下面就给出一个 struct 的例子:

    struct 你的struct的名字
    {
        char variable1;
        short variable2;
        int otherVariable;
        double numberDecimal;
    };
    

    可以看到:struct 的定义以关键字 struct 开始,后面接你自定义的 struct 的名称(比如 Dog,Cat,Person,等)。

    一般来说,在我的代码里,我的 struct 的命名也是遵照变量的命名规则,唯有一点不一样,就是 struct 的名称我会将首字母大写,例如:SchoolName。

    但是我的普通变量一般都是首字母小写,例如:studentNumber。

    这样做只是个人习惯,便于在代码里区分普通的变量和自定义的变量,之后会学到的 enum 和 union,我也是习惯将其名称的首字母大写。

    在 struct 的名字之后,我们需要写上一对大括号,在这对大括号里面写入你的 struct 要包含的各种类型的变量。

    通常说来,struct 的大括号内至少得定义两个变量吧。如果只有一个变量,那定义这个结构体也没什么意义。

    注意:不要忘了,在大括号后面还要加上一个分号(;),
    因为毕竟这个 struct 是一个变量,变量的定义最后都要加分号的。

    如你所见,创建一个自定义的变量也不复杂么。其实结构体就是各种基本类型变量的集合,是一个“大杂烩”。

    当然以后的课程中我们还会看到:结构体的嵌套定义(结构体里包含另一个结构体)。

    结构体的例子


    假设我们需要自定义一个结构体,它储存屏幕上的一个点的坐标。

    下面就给出 2D(D 是英语 dimension 的首字母,表示维度)世界的坐标系的大致印象:

     

    当我们在 2D 世界中做研究时,我们有两个轴:

    • 横坐标轴(从左到右,一般也称为 x 轴)。
    • 纵坐标轴(从下到上,一般也称为 y 轴)。

    只要数学还没有还给小学体育老师,应该都知道 x 和 y 轴的。

    现在,你可以写出一个名叫 Coordinate(表示“坐标”)的 struct 的定义了吗?我看好你!

    可以自己先写,然后对一下我们给出的参考答案:

    struct Coordinate
    {
        int x;  // 横坐标
        int y;  // 纵坐标
    };
    

    很简单,不是吗?我们的 Coordinate 这个 struct 包含了两个变量:x 和 y,都是 int 类型,分别表示横坐标值和纵坐标值。

    当然了,如果你愿意,也可以创建一个表示 3D(三维)空间的点的 struct,只需要在刚才的 Coordinate 这个结构体的基础上加上 z 轴。

    结构体里面的数组


    结构体里面也可以存放数组。

    例如,可以构造一个名叫 Person(表示“人”)的结构体,如下所示:

    struct Person
    {
        char firstName[100];  // 名
        char lastName[100];   // 姓
        char address[1000];   // 地址
    
        int age;  // 年龄
        int boy;  // 性别,布尔值 : 1 = boy(表示“男孩”), 0 = girl(表示“女孩”)
    };
    

    可以看到,这个结构体变量包含五个基本的变量。

    • 前三个分别表示 名、姓和地址,是字符数组。

    • 第四个是年龄。

    • 第五个是性别,是一个“布尔值”(当然,C语言本身没有定义布尔类型(true 或 false),但是可以用数值来“表示”布尔值的真或假),boy 这个 int 变量的值如果为 1,那就是表示男孩;如果为 0,那就是女孩。

    这个结构体可以用于构建一个通讯录程序。当然,你完全可以在这个结构体里再添加其他变量,使其更完善。

    只要内存够,一般来说在一个结构体里没有变量的数目限制。

    3. 结构体的使用


    现在,我们的结构体已经定义在 .h 头文件里了,那么我们就可以在 include(“包含”)此头文件的文件中使用这些结构体了。

    以下展示如何创建一个类型为 Coordinate(我们之前已经定义了这个结构体,表示二维空间的坐标)的变量:

    #include "coordinate.h"  // 假设包含结构体定义的头文件叫 coordinate.h
    
    int main(int argc, char *argv[])
    {
        struct Coordinate point;  // 创建一个 Coordinate 类型的变量,名字是 point
    
        return 0;
    }
    

    如上,我们创建了一个 Coordinate 类型的变量,名字是 point(表示“点”)。

    这个变量自动拥有两个子变量:x 和 y,都是 int 类型,分别表示此二维坐标的横坐标值和纵坐标值。

    你也许要问:“创建结构体变量开头的那个关键字 struct 是必须的吗?”

    是的,是必须的。

    struct 关键字使电脑能够区分基础变量类型(例如 int)和自定义变量类型(例如 Coordinate)。

    然而,每次加 struct 关键字也有点麻烦。所以聪(懒)明(惰)伶(成)俐(性)的 C语言开发者设计了 typedef 关键字。

    当然了,人类的大多数发明都是为了“懒惰”的缘故,能提高效率谁不愿意啊?

    typedef 关键字


    typedef 是 C语言的一个关键字,是 type(表示“类型”)和 define(表示“定义”)的缩合,顾名思义是表示“类型定义”。

    听到“类型定义”,好像很难理解。但其实 typedef 的作用并没有它的含义那么“高深莫测”。

    重新回到刚才定义 Coordinate 这个结构体的 .h 头文件中。我们来加一条由 typedef 开头的命令,目的是为 Coordinate 结构体创建一个别名。

    什么是别名(alias)呢?

    就比如有一个人,真实姓名叫王小明,别名可以是小明,明明,等,但都代表那个人。

    有点类似 C++ 语言的引用的机制。

    所以对别名的操作就是对原先对象的操作。

    比如小时候你上课不乖,老师点名的时候,点到你的小名或者你的真实名字,都是叫的你,你就得去罚站。

    我们就在 Coordinate 结构体的定义之前加这句命令吧,一般习惯加在后面的,但是加在前面也可以:

    typedef struct Coordinate Coordinate;
    
    struct Coordinate
    {
        int x;
        int y;
    };
    

    可以看到,我们新加了一行命令:

    typedef struct Coordinate Coordinate;
    

    为了更好地理解这句命令的作用,我们把它拆为三部分来看:

    1. typedef:说明我们将要创建一个别名。
    2. struct Coordinate:这是我们要为其创建别名的结构体。
    3. Coordinate:这就是要创建的别名。

    所以,上面这句命令的含义就是“从今以后,Coordinate 就相当于 struct Coordinate 了 ”。

    这样做以后,我们就可以不用每次在创建一个新的 Coordinate 结构体的变量时都加上 struct 关键字了。

    所以,我们的 .c 文件中就可以改写为:

    int main(int argc, char *argv[])
    {
        Coordinate point;  // 因为用了 typedef,电脑就清楚地知道此处的 Coordinate 其实就是 struct Coordinate
    
        return 0;
    }
    

    当然,别名不一定要叫 Coordinate,也可以叫作 Coor,也许更不容易混淆。例如:

    typedef struct Coordinate Coor;
    
    struct Coordinate
    {
        int x;
        int y;
    };
    
    Coor coor;  // 创建一个结构体变量
    

    建议大家在平时定义了 struct 类型后,也加一句 typdedef 命令,这样在代码里就不用每次新建一个此类型的变量时都要在开头写 struct 关键字了。

    很多程序员都会这么做。
    因为一个好的程序员是懂得如何“偷懒”的程序员,这和一个懒惰的程序员是有区别的。
    我们要使代码"write less,do more"(用尽量少的代码做更多的事)。

    当然,上面的代码块可以简写为:

    typedef struct struct的名字
    {
      // struct 的内容
    } 别名;
    

    所以上面 Coordinate 的代码块可以简写为:

    typedef struct Coordinate
    {
        int x;
        int y;
    } Coordinate;
    

    注意:之后我们的示例代码,有时会出现例如

    Person player1;
    

    这样的形式,那就是假定我们之前已经用了 typedef 了:

    typedef struct Person Person;
    

    这样就可以省略开头的 struct 关键字,不需要再写成:

    struct Person player1;
    

    修改 struct 的成员变量


    既然我们的 point 变量(是 Coordinate 类型的,希望大家还没晕)已经创建好了,那我们就可以修改它的成员的值了。

    我们如何访问 point 的两个成员 x 和 y 呢?如下所示:

    int main(int argc, char *argv[])
    {
        Coordinate point;
    
        point.x = 10;
        point.y = 20;
    
        return 0;
    }
    

    这样,我们就顺利地修改了 point 的两个成员的值,使其 x 坐标为 10,y 坐标为 20。

    因此我们的点就位于坐标系的(10, 20)处了。

    所以,为了能访问到结构体的某个成员,我们可以这样做:

    结构体实例名称.成员名
    

    中间的点(.)表示“从属”关系。

    如果有面向对象编程基础的朋友,就会觉得:这与“类和对象”也太像了吧。

    是的,其实我们可以用 struct 来“模拟”类。

    如果我们用之前创建的 Person 这个结构体来举例的话:

    int main(int argc, char *argv[])
    {
        Person user;  // user 表示“用户”
    
        printf("你姓什么 ? ");
        scanf("%s", user.lastName);
    
        printf("你名叫什么 ? ");
        scanf("%s", user.firstName);
    
        printf("原来你的名字是 %s%s,失敬失敬\n", user.lastName, user.firstName);
    
        return 0;
    }
    

    运行输出:

    你姓什么?王
    你名叫什么?小明
    原来你的名字是 王小明,失敬失敬
    

    我们把 user.lastName 传给 scanf,使得用户输入的值直接修改 user 的 lastName 成员;我们对 user.firstName 也是如此。

    当然我们也可以再添加对 address,age,boy 的赋值。

    当然了,你也许会说:“我不知道结构体的使用,我用两个单独的字符串变量 lastName 和 firstName 不是也可以做到和上述程序相同的事么?”

    是的,但是用结构体的好处就是我们可以创建此结构体的变量,将很多相关联的数据封装在一起,成为一个整体,而不是零散地定义。

    比如定义了 Person 这个结构体之后,凡是用 Person 来创建的变量,里面都自动包含了 lastName,firstName,address,age 和 boy 这五个变量,非常方便。

    比如我们可以这样创建:

    Person player1, player2;  // 之前已经用 typedef( typedef struct Person Person; )
    

    在 player1 和 player2 中都包含 lastName,firstName,address,age 和 boy 这五个变量。

    我们也可以更“偷懒”一些:创建结构体数组。例如:

    Person players[2];
    

    这样,我们就可以很方便的访问 players[1] 当中的变量了,例如:

    players[1].lastName = "xiaoming";
    

    用结构体数组的好处是可以方便地使用循环,等等。

    自测小练习


    创建一个名叫 CoderHub(「程序员联盟」公众号)的结构体,在定义里放入你想创建的变量。然后创建此结构体的一个数组,用循环的方式给变量赋值,再用循环的方式打印出其中变量的信息。

    结构体的初始化

    之前的课程里,我们建议对于基本变量,数组和指针,最好在创建的时候对其初始化。结构体也不例外。

    初始化有一个很大的好处,就是避免此变量里存放“任意数据”。

    事实上,一个变量在创建时,如果没有初始化,那么它会取当时在内存那个位置所存的值,所以这个值的随机性是很大的。

    我们来回忆一下,不同变量的初始化应该怎么做:

    • 基础变量(int,double,char,等):初始化为 0。

    • 指针:初始化为 NULL。事实上,NULL 位于 stdlib.h 标准库头文件中,是用 #define 预处理命令定义的一个常量。它的值通常是 0。虽然是 0,但是有多种定义形式,例如:

    #define NULL 0
    #define NULL 0L
    #define NULL ((void *) 0)
    

    但是我们只要每次用 NULL 就好了,为了清楚表明这是指针变量,而不是一般变量。

    • 数组:将每一个成员变量初始化为 0。

    那么对于我们的“朋友” 结构体,我们怎么初始化呢?

    其实结构体的初始化也很简单,与数组的初始化很类似。我们可以像下面这样定义:

    Coordinate point = {0, 0};
    

    这样,我们就依照顺序将 point.x 和 point.y 都初始化为 0 了。

    对于像 Person 这样的结构体,里面的变量类型有 char 型数组和 int,那么我们可以将 char 型数组初始化为 ""(双引号中间为空)。

    我们可以像这样初始化一个字符串,在 C语言探索之旅 | 第二部分第四课:字符串 那一课忘记提了。不过,我想现在提还不算晚吧。

    所以我们就可以这样来初始化我们的 Person 结构体变量:

    Person player = {"", "", "", 0, 0};
    

    然而,我们也可以这样来初始化一个结构体变量:创建一个函数,比如叫 initializeStruct,可以为每一个传递给它的结构体做初始化,这样就方便很多,特别是当结构体中的变量很多时。

    之前指针那一章我们也已经学了,如果我们对函数传递普通变量,那么因为 C语言的函数参数传递方式是值传递,所以它会对传给它的函数参数做一份拷贝,这样函数里面修改的其实是那一份拷贝,真正的实参并没有被改变。

    为了让实参实实在在被修改,我们需要用到指针,也就是传递此变量的地址。

    对于结构体,也需要这样。因此,接下来我们就来学习如何使用结构体指针。开始难起来咯,准备好了吗?

    4. 结构体指针


    结构体指针的创建其实和普通的指针变量创建没什么区别。例如:

    Coordinate *point = NULL;
    

    上面的代码就创建了一个叫做 point 的 Coordinate 结构体指针变量(Coordinate 是我们上面定义的表示坐标的一个结构体)。

    我们再来提醒一次:

    一般推荐写成:

    Coordinate *point = NULL; // 星号挨着指针变量名字
    

    而不推荐写成:

    Coordinate* point = NULL;  // 星号挨着结构体名,这种写法不好!
    

    在指针的创建中,我们推荐第一种写法。

    因为用第二种写法,如果你在一行上创建好几个指针变量时,会容易忘记在第二个之后的变量前加 * 号。例如,容易写成这样:

    Coordinate* point1 = NULL, point2 = NULL;   // 编译会出错
    

    但这样编译会出错,因为 point2 其实是 Coordinate 结构体变量,而不是 Coordinate 结构体指针变量!

    所以我们建议这样写:

    Coordinate *point1 = NULL, *point2 = NULL;
    

    在以前的课程中,对于基础类型的指针变量,我们也是这样建议:

    int *number1 = NULL, *number2 = NULL;
    

    特别是 int 型的指针,还很不容易察觉到错误,如果写成:

    int* number1 = NULL, number2 = NULL;
    

    编译器是不会报错的。因为 NULL 的值就是 0,可以赋给 number2 这个 int 型变量(注意:上面的 number2 不是 int 指针)。

    回顾总是很好的(“伤心总是难免的…”)。

    结构体作为函数参数


    这里,我们主要来学习如何将一个结构体指针(为什么是传结构体指针而不是传结构体,可以看之前的解释)传给一个函数(作为参数),使得函数内部可以真正修改此结构体。

    我们来看一个实例:

    #include <stdio.h>
    
    typedef struct Coordinate
    {
        int x;  // 横坐标值
        int y;  // 纵坐标值
    } Coordinate;
    
    void initializeCoordinate(Coordinate *point);  // 函数原型
    
    int main(int argc, char *argv[]) {
        Coordinate myPoint;
    
        initializeCoordinate(&myPoint);  // 函数的参数是 myPoint 变量的地址
    
        return 0;
    }
    
    // 用于初始化结构体变量
    void initializeCoordinate(Coordinate *point) {
        // 结构体初始化的代码
    }
    

    上面的 initializeCoordinate 函数体内,我们将放置初始化结构体的成员变量的代码。

    我们按顺序来看一下这段代码:

    • 首先,我们定义了一个结构体,叫做 Coordinate,里面包含两个变量,x 和 y。

    • 我们在 main 函数中创建了Coordinate 结构体的变量,名字叫 myPoint。

    • 我们将 myPoint 的地址传递给 initializeCoordinate 这个函数。

    • 接下来,我们就在 initializeCoordinate 函数中添加初始化 x 和 y 变量的代码吧:

    void initializeCoordinate(Coordinate *point){
        *point.x = 0;
        *point.y = 0;
    }
    

    point 前面的 * 号是必不可少的噢。因为,传进函数的参数是一个结构体指针,我们要取到此结构体,就需要用到“解引用”符号:星号(*)。

    但是,认真的读者看出上面这个函数中的错误了吗?

    我们的初衷是想要:先用 * 号解引用 point 这个结构体指针,取到结构体,然后再用 . 号取到其中的变量 x 和 y。但是如果按上面的写法,其实效果相当于如下:

    *(point.x) = 0;
    *(point.y) = 0;
    

    因为 . 号的优先级是高于 * 号的。

    有兴趣可以看一下 C语言运算符的优先级,不过之前的课我们也说过了,记不清怎么办呢?加括号就解决啦。

    上面的代码编译是通不过的,因为结构体指针 point 并没有成员叫 x 和 y,而且,对于结构体指针我们也不能用 . 号来取到什么值。

    因此,我们需要修改一下。改为如下就可以了:

    void initializeCoordinate(Coordinate *point) {
        (*point).x = 0;
        (*point).y = 0;
    }
    

    这样就对了。用括号去掉了运算符优先级的影响。

    但是,之前也说过:程序员是懂得偷懒的一群人。

    如果每次要取结构体的成员变量都要这么麻烦,先用 * 号,还要加括号,再用 . 号。想想都要让 Denis Ritchie(C语言的作者)老爷子醉了。他是决不允许这种事发生的,因此,他就定义了一个新的符号: ->(一个箭头。是的,就是这么“霸气侧漏”)。

    用法如下:

    point->x = 0;
    

    就相当于:

    (*point).x = 0;
    

    是不是简便了很多?

    记住:这个符号,只能用在指针上面。

    因此,我们的函数可以改写为:

    void initializeCoordinate(Coordinate *point) {
        point->x = 0;
        point->y = 0;
    }
    

    我们在 main 函数里也可以这样写:

    int main(int argc, char *argv[])
    {
        Coordinate myPoint;
        Coordinate *myPointPointer = &myPoint;
    
        myPoint.x = 10;  // 用结构体的方式,修改 myPoint 中的 x 值
        myPointPointer->y = 15;  // 用结构体指针的方式,修改 myPoint 中的 y 值
    
        return 0;
    }
    

    结构体是 C语言中一个非常好用且很重要的概念,希望大家好好掌握!

    当然,还有不少知识细节,就要大家自己去看 C语言的经典教材了,例如《C程序设计语言》(不是谭浩强那本《C语言程序设计》!而是 C语言作者写的经典之作),《C和指针》,《C专家编程》,《C语言深度解剖》,《C陷阱和缺陷》,等等。

    5. union


    union 是“联合”的意思,是 C语言的关键字,也有的书上翻译为“共用体”。

    我们可以来写一个 union 的例子。

    union CoderHub
    {
        char character;
        int memberNumber;
        double rate;
    };
    

    乍看之下,和 struct 没什么区别么。但是真的没有区别吗?

    假如我们用 C语言的 sizeof 关键字(size 表示“尺寸,大小”,of 表示“…的”)来测试此 union 的大小(大小指的是在内存中所占的字节(byte)数,一个字节相当于 8 个 bit(二进制位)):

    #include <stdio.h>
    
    typedef union CoderHub
    {
        char character;  // 大小是 1 个字节
        int memberNumber;  // 大小是 4 个字节
        double rate;  // 大小是 8 个字节
    } CoderHub;
    
    int main(int argc, char *argv[]){
        CoderHub coderHub;
    
        printf("此 union 的大小是 %lu 个字节\n", sizeof(coderHub));
    
        return 0;
    }
    

    运行程序,输出:

    此 union 的大小是 8 个字节
    

    假如我们对结构体也做一次测试,对比一下:

    #include <stdio.h>
    
    typedef struct CoderHub
    {
        char character;  // 大小是 1 个字节
        int memberNumber;  // 大小是 4 个字节
        double rate;  // 大小是 8 个字节
    } CoderHub;
    
    int main(int argc, char *argv[]){
    
        CoderHub coderHub;
    
        printf("此 struct 的大小是 %lu 个字节\n", sizeof(coderHub));
    
        return 0;
    }
    

    运行程序,输出:

    此 struct 的大小是 16 个字节
    

    为什么我们自定义的 union 的大小是 8 个字节,而 struct 是 16 个字节呢?

    这就涉及到 union(共用体)和 struct(结构体)的区别了。

    struct 的大小是其中所有变量大小的总和。

    但是你会说:“不对啊, 1 + 4 + 8 = 13,为什么 sizeof(coderHub) 的值为 16 呢?”

    好问题!这个有点复杂,涉及到内存对齐的问题,我们以后再说。如果你一定要知道,那是因为内存对齐使得第一个 char 变量对齐了第二个 int 变量的空间,也变成了 4,如此一来:4 + 4 + 8 = 16。

    有兴趣的读者可以去参考《C语言深度解剖》的解释。

    在嵌入式编程等内存有限的环境下,需要考虑内存对齐,以节省空间。

    union 的大小等于其中最大(sizeof() 得到的值最大)的那个变量的大小。所以我们就知道了,其实 union 的储存是这样的:其中的每个变量在内存中的起始地址是一样的,所以 union 同一时刻只能存放其中一个变量,union 的大小等于其中最大的那个变量,以保证可以容纳任意一个成员。

    union 适合用在很多相同类型的变量集,但是某一时刻只需用到其中一个的情况,比较节省空间。

    6. enum


    看完了 struct(结构体)和 union(联合),我们最后来学习很常用的一个自定义变量类型:enum。

    enum 是 enumeration(表示“枚举”)的缩写,也是一个 C语言关键字。

    枚举是一个比较特别的自定义变量类型。当初我学 C语言时,一开始还真有点不理解。但用得好,却非常实用。

    我们之前学了:结构体里面包含了多个可以是不同类型的成员变量(一说“成员”就有点面向对象的感觉 :P)。

    但是 enum(枚举)里面是一系列可选择的值。也就是说每次只能取其中一个值,听着和 union 有点类似啊。但是 enum 和 union 还是有区别的。

    我们来举一个例子就知道区别了:

    typedef enum Shape Shape;
    
    enum Shape   // shape 表示“身材、体型”
    {
        THIN,   // thin 表示“瘦”
        MEDIUM,   // medium 表示“中等”
        FAT   // fat 表示“胖”
    };
    

    所以,我们定义了一个名叫 Shape 的 enum 变量。其中有三个值,分别是 THIN,MEDIUM 和 FAT(身材有瘦,中等和胖之分)。不一定要大写,只是习惯。

    那我们怎么来创建 enum 变量呢?如下:

    Shape shape = MEDIUM;
    

    shape 这个变量,我们在程序里也可以再将其修改为 THIN 或者 FAT。

    将数值赋给 enum 的成员


    大家看到 enum 和 union 以及 struct 的区别了吗?是的,enum 的定义里,每个成员没有变量类型(int,char,double,之类)!

    很奇怪吧。想起来为什么 enum 的成员习惯用大写了吗?

    对,就是因为 enum 的每个成员都不是变量,而是常量!但是 enum 的机制和常量定义以及 #define 还是有些区别:

    像上面的代码:

    typedef enum Shape
    {
        THIN,
        MEDIUM,
        FAT
    } Shape;
    

    编译器会自动为其中的每一个成员绑定一个常量值,我们写程序测试一下:

    #include <stdio.h>
    
    typedef enum Shape Shape;
    
    enum Shape
    {
        THIN,
        MEDIUM,
        FAT
    };
    
    int main(int argc, char *argv[]) {
        Shape shape = THIN;
        printf("THIN = %d\n", shape);
    
        shape = MEDIUM;
        printf("MEDIUM = %d\n", shape);
    
        shape = FAT;
        printf("FAT = %d\n", shape);
    
        return 0;
    }
    

    运行程序,输出:

    THIN = 0
    MEDIUM = 1
    FAT = 2
    

    看到了吗?编译器自动给这三个成员赋值 0,1 和 2。如果没有指定 enum 成员的值,那么它们的值是从 0 开始,依次加 1。

    我们也可以自己来定义 enum 成员的值,不一定要每次让编译器给我们自动分配。

    我们可以这样写:

    typedef enum Shape
    {
        THIN = 40,
        MEDIUM = 60,
        FAT = 90
    } Shape;
    

    这样,我们就自己给每个成员定义了值。

    我们也可以让编译器为我们自动分配几个值,再自己定义几个值,例如:

    typedef enum Shape
    {
        THIN,
        MEDIUM,
        FAT = 90
    } Shape;
    

    上面,我们没有为 THIN 和 MEDIUM 赋值,那么编译器会将他们赋值为 0 和 1。

    而 FAT,因为我们已经指定了其值为 90,所以 FAT 就等于 90。

    enum 和 #define 的区别


    是不是觉得 enum 和用 #define 来定义的常量是有些类似呢?

    其实,还是有些不同的:

    • #define 宏常量(或预处理常量)是在预处理阶段进行简单替换,枚举常量则是在编译的时候确定其值。

    • 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。

    • 枚举可以一次定义大量相关的常量,而 #define 宏一次只能定义一个。

    7. 总结


    1. 结构体(struct)是一种自定义的变量类型,完全由我们自由发挥,自己定制(走的是“高级定制”的路线啊),与 int,double 等基础变量类型有所区别。结构体的使用可以让我们的 C语言程序更加灵活,可以做更多事。

    2. 结构体里包含成员变量,通常是基础变量类型的变量,如 int,double 等变量,但也可以有指针变量,数组,甚至其他的结构体变量。

    3. 为了访问到结构体的成员变量,我们可以用普通的结构体方式访问:结构体变量名称.成员变量名(中间用一个“点”连接)。

    4. 我们也可以用特别简便的结构体指针的方式来访问结构体的成员变量:结构体指针变量名->成员变量名(中间用一个“箭头”连接)。

    5. union(“共用体”,或“联合”)和 struct 的最大不同就是:union 的大小是其中容量最大的那个成员变量的大小,而结构体的大小是每一个成员变量的总和(还要考虑内存对齐)。union 一次只能取其中一个变量。

    6. enum(枚举)一次只能取其中的一个成员的值,这一点和 union 有些类似。但是 enum 的成员都是常量,而不是变量。而且 enum 的成员如果没有指定数值,编译器会按照递增顺序为每一个变量赋值,从 0 开始。

    8. 第二部分第七课预告


    今天的课就到这里,一起加油吧!

    下一课:C语言探索之旅 | 第二部分第七课:文件读写


    我是 谢恩铭,公众号「程序员联盟」(微信号:coderhub)运营者,慕课网精英讲师 Oscar 老师,终生学习者。
    热爱生活,喜欢游泳,略懂烹饪。
    人生格言:「向着标杆直跑」

    展开全文
  • 难道在C语言中一个结构不能包含指向自己的指针吗? o 2.7 怎样建立理解非常复杂的声明?例如定义一个包含 N 个指向返回指向字符的指针的函数的指针的数组? o 2.8 函数只定义了一次, 调用了一次, 但编译器提示...
  • 我不是技术大牛,也从没有贡献过一行代码给Liunx,所以下面的这些文字对我来说“太沉重了”。 我们能够从TIOBE看出每种编程语言的流行程度。...纯粹的不叫面向对象,我觉得泛型模板更面向对象。 我从
     
    

    我不是技术大牛,也从没有贡献过一行代码给Liunx,所以下面的这些文字对我来说“太沉重了”。

    我们能够从TIOBE看出每种编程语言的流行程度。在面向对象程序设计已经日臻成熟的今天,C语言一直傲立三甲之中,它是个奇迹。

    纯粹的类不叫面向对象,我觉得泛型和模板更面向对象。
    我从DOS下的Turbo C和Turbo PASCAL一路走来,跟大家一样,经历了编程语言孰优孰劣的火热年代,经历了IDE百家齐鸣的辉煌历程,亲身体验了基于对象和纯面向对象的时代变迁,反而让我对面向对象有疑惑了。

    我不知道几乎月月排在TIOBE榜首的Java是否是纯粹的面向对象语言。但是,我无意挑起语言之争,我自己也在啃《Java编程思想》,而且这真的是一本好书,Java程序员应该人手一本的那种。当昔日红红火火的UML渐行渐远之后,当B/S结构趋于平淡,而胖客户端依然坚挺之际,更彰显了不同平台和语言之间的本质区别。

    编程就是解决复杂问题的过程,这个过程中我们使用面向对象的方式将事物封装,通过继承和多态进行拓展,程序员和客户多年间从中获益匪浅。但是每每调用Java库中的方法时,我的思想却总是回到过程式开发中去。每每看到“纯粹”的使用类的方式的C++代码时,我心里总是别扭,这是C++的本来面貌吗?亦或是我们自己刻意的按照面向对象的方式“塑造”的结果?有类就是面向对象吗?我老是觉得,为了面向对象而拼凑的类代码,跟集合没什么区别。但是泛型给我的感觉更像面向对象,无论泛型的代码多么“丑陋”。C++的骨子里还是过程式的开发思想,C++之父多年来,以及在《C++程序设计语言》中也只是说C++支持面向对象的程序设计模式,这里面的语义很丰富也很单一。

    使用Java是出于工作的需要,但不是面向对象的需要。C++也是如此。但是C呢?C有没有可能面向对象呢?答案是肯定的。面向对象程序设计从没有要求过程序代码的书写方式,C这种中级语言承上启下,排在Java之后(有时也会跑掉前面去,呵呵),在C++之上,它的地位相当牢固,根基相当底层。比面向对象,C++肯定比Java强(C++几乎支持面向对象的方方面面,Java借鉴了C++的成功经验,回避了面向对象中的艰涩部分),比性能C++不可能比C强,论底层编程能力,C++和Java都不是C的对手。

    不是要谈语言的优劣,我们在工作中继续好好使用我们的语言就是了。但是我老觉得平时大多数时候我们在用类的方式干着过程的活,是不是呢?我不知道。

    展开全文
  • 《你必须知道的495个C语言问题》

    热门讨论 2010-03-20 16:41:18
    1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 2  1.4 新的64位机上的64位...
  • 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 2  1.4 新的64位机上的64位...
  • 你必须知道的495个C语言问题(PDF)

    热门讨论 2009-09-15 10:25:47
    2.5 在C 中是否有模拟继承等面向对象程序设计特性的好方法? . . . 7 i 目录ii 2.6 我遇到这样声明结构的代码: struct name f int namelen; char namestr[1];g; 然后又使用一些内存分配技巧使namestr 数组用起 来...
  • 书中专门为学生提供了一个用C语言编写的实习项目,包括前端后端设计,学生可以在一学期内创建一个功能完整的编译器。  本书适用于高等院校计算机及相关专业的本科生或研究生,也可供科研人员或工程技术人员参考。...
  • 20.1 没有资源约束时的循环调度 20.2有资源约束的循环流水 20.2.1模调度 20.2.2寻找最小的启动间距 20.2.3其他控制流 20.2.4编译器应该调度指令吗 20.3分支预测 20.3.1静态分支预测 20.3.2编译器应该预测...
  • 现代编译原理C语言描述-虎书中文版

    热门讨论 2010-04-11 16:47:52
    20.1 没有资源约束时的循环调度 332 20.2 有资源约束的循环流水 336 20.2.1 模调度 337 20.2.2 寻找最小的启动间距 338 20.2.3 其他控制流 340 20.2.4 编译器应该调度指令吗 340 20.3 分支预测 341 ...
  • 面向对象【5】

    2012-11-16 10:50:16
     类成员函数是属于类中的函数,类具有了类函数就可以体现出面向对象程序的设计方式,但是想想如果没有类函数的类的话,那  不就我们以前学过的C语言当中的结构体一样吗?所以在面向对象程序设计中类函数是非常...

    类的成员函数
     成员函数的性质:
        类的成员函数是函数的一种,在前面的几篇文中已经看到这些函数了,和普通的函数基本没有什么不同,其主要不同是
      类成员函数是属于类中的函数,类具有了类函数就可以体现出面向对象程序的设计方式,但是想想如果没有类函数的类的话,那
      不就和我们以前学过的C语言当中的结构体一样吗?所以在面向对象程序设计中类函数是非常重要的。
      
     在类外定义成员函数:
      例子:

    #include <iostream>
    			using namespace std;
    			class myclass
    			{
    				private:
    					int a,b;
    				public:
    					void setab(int,int);
    					void display();
    			}
    	void myclass::setab(int x, int y)   //类外定义成员函数;
    	{
    		a = x;
    		b = y;
    	return;
    	}
    	
    	void myclass::display() 			//类外定义成员函数;
    	{
    		cout<<"a:"<<a<<endl
    			<<"b:"<<b<<endl;
    	return;
    	}
    	....
    	...........
    	.................



     上面的例子就是在类外定义成员函数的例子,在类外定义成员函数应该注意以下几点:
       1.类函数的类型
       2.类函数属于哪个类
       3.类函数的原型
     如上面的例子中,setab()与display()类函数都是无返回值的类函数void,所以在类外定义时首先要将类函数的
     返回类型写上,然后再写这个类函数是属于哪个类,如void myclass::setab(int x, int y)这个类函数是属于类
     myclass的,所以在写完返回类型后再写上哪个类,之后再通过作用域限定符“::“写上类函数的函数名称和具体
     的参数的类型和形参名,至于函数体的写法和前面学过的函数的体的写法是一样的;
     
     关于内置成员函数
      类的成员函数也是可以指定为内置函数。在类体中定义的成员函数一般规模都是很小的,而系统调用函数过程
      所花费的时间是很大的。调用一个函数的时间开销远远大于小规模函数体中全部语句的挨执行时间。为了减少
      时间开销我们可以将一些不复杂的成员函数声明为一个内置成员函数,但是一般在什么时候定义内置成员函数
      为好呢?下面几点:
         1.函数没有循环操作;
         2.简单的操作如,只输出或是输入一些数据的操作;
      最后一点,值得注意:
        在类体中,声明一个成员函数,如果该成员函数的规模小,并且不是复杂的操作的成员函数系统会自动
        将这个成员函数声明为内置成员函数。还有一种情况就是如果成员函数小,但是你在类外定义这个成员
        函数,系统是不会将这个成员函数设置为内置成员函数的,这点值得注意;
        
     

    展开全文
  • 面向对象程序设计

    2021-06-10 10:41:23
    面向对象程序设计是当前非常流行的一种程序设计,学过C语言我们知道C是面向过程的程序设计语言,学过JavaCpp我们知道这两种程序设语言是面向对象程序设计。对面向对象我们听到最多的是什么呢?就是面向对象就是...
  • 即不能被子访问,那么子类通过继承得到了父类的属性,只能通过setter getter 方法区访问父类的私有变量,那么这个私有变量不属于子类,那么在实例化这个子类的时候就没有这个私有变量,那么通过继承过来的setter ...
  • 典型的低级的错误之一,就是无意识的一个无限制的扩充,完全没有考虑到的多层结构(基类-派生),需要属性或方法便在中 增加,虽然也用到了多态、重载等一些OO的设计方式,但最后这个庞大无比,除了在当前...
  • 及失去了C的简洁清晰高效,也不完全具备C++的面向对象的特性。这根本不能叫C++程序。(我想有时间重写一下以前代码也会有很多收获,温故而知新吗)CC++在编程思想上是相互矛盾的。这也就是说如果你想学C++,完全...
  • 文章目录构造函数默认构造函数的三种不适用情况第一种第二种第三种 class Date { }; 可以看到,上面那个类没有...我们之前c语言中自己实现的init函数类似。但是有一点不同的是,init是在我们创建完后才自己调用,
  • 这种转换适用于面向过程的没有类的概念的c语言的转换,然而这样的转换符也能不分青红皂白的应用于类和类的指针,没有安全检查。2.const_cast用法:const_cast (expression)用于修改类型的const或volatile属性,一般...
  • 经验总结

    2018-01-01 14:31:00
     在C语言中的结构体就是C++的类和对象,只不过,在C++中,对类有封装(就是public private和protected) 但是对于结构体没有所谓的封装。 C++的类就是C语言的结构体,C++的对象就是C的结构体变量。 ...
  • 现代C++程序设计

    2012-07-10 23:29:45
    2.9 开始使用类和对象、C++string类 2.10 练习 复习题 第3章 控制语句和循环 3.1 关系运算符和逻辑运算符 3.2 if语句 3.2.1 if-else语句 3.2.2 问题分析:在if语句中使用大括号 3.2.3 if-else if-else语句 3.2.4 ...
  • 当你用着java里面的容器很爽的时候,你有没有想过,怎么ArrayList就像一个无限扩充的数组,也好像链表之的。好用吗?好用,这就是数据结构的用处,只不过你在不知不觉中使用了。 现实世界的存储,我们使用的工具...
  • 当你用着java里面的容器很爽的时候,你有没有想过,怎么ArrayList就像一个无限扩充的数组,也好像链表之的。好用吗?好用,这就是数据结构的用处,只不过你在不知不觉中使用了。 现实世界的存储,我们使用的工具...
  • 当你用着java里面的容器很爽的时候,你有没有想过,怎么ArrayList就像一个无限扩充的数组,也好像链表之的。好用吗?好用,这就是数据结构的用处,只不过你在不知不觉中使用了。 现实世界的存储,我们使用的工具...
  • 当你用着java里面的容器很爽的时候,你有没有想过,怎么ArrayList就像一个无限扩充的数组,也好像链表之 的。好用吗?好用,这就是数据结构的用处,只不过你在不知不觉中使用了。 现实世界的存储,我们使用的...
  • 数据结构小复习

    2020-07-29 13:54:00
    当你用着java里面的容器很爽的时候,你有没有想过,怎么ArrayList就像一个无限扩充的数组,也好像链表之的。好用吗?好用,这就是数据结构的用处,只不过你在不知不觉中使用了。 现实世界的存储,我们使用的工具...
  • 2.9 开始使用类和对象、C++string类 2.10 练习 复习题 第3章 控制语句和循环 3.1 关系运算符和逻辑运算符 3.2 if语句 3.2.1 if-else语句 3.2.2 问题分析:在if语句中使用大括号 3.2.3 if-else if-else语句 3.2.4 ...

空空如也

空空如也

1 2 3
收藏数 55
精华内容 22
关键字:

c语言没有类和对象吗

c语言 订阅