首页 > 其他分享 >C语言面向对象

C语言面向对象

时间:2024-09-06 17:51:13浏览次数:15  
标签:struct stu int void C语言 面向对象 student super

我们在编写程序时,通常采用以下步骤:

  1. 将问题的解法分解成若干步骤

  2. 使用函数分别实现这些步骤

  3. 依次调用这些函数

这种编程风格的被称作面向过程。除了面向过程之外,还有一种被称作面向对象的编程风格被广泛使用。面向对象采用基于对象的概念建立模型,对现实世界进行模拟,从而完成对问题的解决。

C语言的语法并不直接支持面向对象风格的编程。但是,我们可以通过额外的代码,让C语言实现一些面向对象特性。在这一节当中,我们将探究什么是面向对象,以及怎样用C语言来实现它。

单纯理论上的讨论可能比较难以理解,为了能够让我们的讨论能够落地到实际中,我们选取学校为场景,展开对面向对象风格编程的讨论。

一般而言面向对象风格的编程具有以下3大特性:

  • 封装

  • 继承

  • 多态

我们将以这3个特性为线索,讨论C语言如何面向对象编程

封装

我们来看看学校里面最重要的主体是什么?是学生。学生肯定拥有很多属性,比如学生的学号、姓名、性别、考试分数等等。自然地,我们会声明一个结构体用于表示学生。

struct student {

    int id; // 学号

    char name[20]; // 姓名

    int gender; // 性别

    int mark; // 成绩

};

学生的学号由 入学年份 、 班级 、 序号 拼接构成。

例如,某一个同学的是 2022 年入学的 123 班的 26 号学生。那么,它的学号为 202212326 。

为了方便设置学号,我们有一个 makeStudentId 函数,参数为 入学年份 、 班级 、 序号 ,它将这些数据拼接成字符串,再将字符串转换为整型数据,最后将这个整型数据作为学生的 id 并返回。

int makeStudentId(int year, int classNum, int serialNum)

{

    char buffer[20];

    sprintf(buffer, "%d%d%d", year, classNum, serialNum);

    int id = atoi(buffer);

    return id;

}

sprintf和printf函数类似,printf函数会将占位符"%d%d%d"替换为其后的参数,将结果打印到控制台上。而sprintf不会将结果打印在控制台上,而是将结果存放在第一个参数buffer所指示的字符数组当中。

函数atoi能将buffer指示的字符串转换为整型并返回结果。

性别在结构体中存储为整型数值,0代表女生、1代表男生。而显示时,我们希望0显示为女,1显示为男。因此,还需要有一对用于操作性别的函数。在函数命名中,使用numGender代表使用整型表示的性别。strGender代表使用字符串表示的性别。

我们将定义两个函数:

numGenderToStrGender表示,将整型表示的性别转换为字符串表示的性别。

strGenderToNumGender表示,将字符串表示的性别转换为整型表示的性别。

const char* numGenderToStrGender(int numGender)

{

    if (numGender == 0)
    {
        return "女";
    }

    else if (numGender == 1)
    {
        return "男";
    }
    return "未知";

}

int strGenderToNumGender(const char* strGender)
{

    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    return numGender;
}

我们将使用以下方式,调用这个结构体和这3个函数。

int main()

{

    struct student stu;

    // 设置数值

    // 学号:202212326

    // 姓名:小明

    // 性别: 男

    // 成绩:98

    stu.id = makeStudentId(2022, 123, 26);

    strcpy(stu.name, "小明");

    stu.gender = strGenderToNumGender("男");

    stu.mark = 98;

    // 打印这些数值

    printf("学号:%d\n", stu.id);

    printf("姓名:%s\n", stu.name);

    const char* gender = numGenderToStrGender(stu.gender);

    printf("性别:%s\n", gender);

    printf("分数:%d\n", stu.mark);

    return 0;

}

现在,我们使用面向过程风格写了3个函数和一个结构体,并且调用了这些函数,将函数返回的结果赋值给了结构体。接下来,让我们以面向对象风格来重新审视这段代码。

在面向对象风格中,结构体被看做数据(data),而操作数据的函数称作方法(method)。目前函数和数据是分离的,函数并不直接操作数据,我们需要拿到函数返回的结果,再将其赋值给数据。面向对象风格编程的第一大特性——封装,它希望方法直接操作数据,并且将数据和方法结合在一起,它们构成一个整体。而这个整体被称作对象。

此外,还有一个方法命名上的规则。一般来说,获取数据的方法会被命名为getXXX,设置数据的方法会被命名为setXXX。

我们对这3个函数做如下修改:

  1. 将函数的第一个参数设置为struct student *,让函数直接操作student结构体。

  2. 修改函数名,获取数据的方法命名为getXXX,设置数据的方法命名为setXXX。

void setStudentId(struct student* s, int year, int classNum, int serialNum)
{
    char buffer[20];
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    int id = atoi(buffer);
    s->id = id;
}

const char* getGender(struct student* s)
{
    if (s->gender == 0)
    {
        return "女";
    }
    else if (s->gender == 1)
    {
        return "男";
    }
    return "未知";
}

void setGender(struct student* s, const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    s->gender = numGender;
}

现在,我们用修改后的函数,直接操作student结构。

int main()

{

    struct student stu;

    // 学号:202212326

    // 姓名:小明

    // 性别: 男

    // 分数:98

    setStudentId(&stu, 2022, 123, 26);

    strcpy(stu.name, "小明");

    setGender(&stu, "男");

    stu.mark = 98;

    // 打印这些数值

    printf("学号:%d\n", stu.id);

    printf("姓名:%s\n", stu.name);

    const char* gender = getGender(&stu);

    printf("性别:%s\n", gender);

    printf("分数:%d\n", stu.mark);

}

目前,函数可以直接操作数据了。但是,函数和数据依然是两个独立的部分。我们要将函数和数据结合到一起,这样,这个整体就能被称作对象,函数可以称作属于这个对象的方法。

大多数面向对象语言都提供了以下的格式调用一个对象的方法。

对象.方法(对象指针,参数1,参数2, 参数3...)

接下来,我们举几个这种格式的例子:

stu.setGender(&stu, "男");

以上代码中,对象为stu,方法为setGender。通过对象 + 点 + 方法的形式,可以调用属于对象stu的setGender方法。在方法的参数中传入性别男。这样,方法会把性别男转换为整形,并设置到对象stu的数据当中。

const char* gender = stu.getGender(&stu);

以上代码中,对象为stu,方法为getGender。通过对象 + 点 + 方法的形式,可以调用属于对象stu的getGender方法。getGender方法从对象数据中获取整形表示的性别,并返回性别对应的字符串。

在C语言中,若要实现对象 + 点 + 方法的形式,我们可以借助于函数指针。

在结构中,声明这3个函数的函数指针。

struct student {

    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);

    const char* (*getGender)(struct student* s);

    void (*setGender)(struct student* s, const char* strGender);

    int id; // 学号

    char name[20]; // 姓名

    int gender; // 性别

    int mark; // 分数

};

为了让函数指针有正确的指向,我们需要通过一个initStudent函数,为结构体初始化。

void initStudent(struct student* s)

{

    s->setStudentId = setStudentId;

    s->getGender = getGender;

    s->setGender = setGender;

}

现在,我们可以使用对象 + 点 + 方法的形式,调用对象的方法了。

struct student stu;

// 初始化student

initStudent(&stu);

// 学号:202212326

// 姓名:小明

// 性别: 男

// 分数:98

stu.setStudentId(&stu, 2022, 123, 26);

strcpy(stu.name, "小明");

stu.setGender(&stu, "男");

stu.mark = 98;

// 打印这些数值

printf("学号:%d\n", stu.id);

printf("姓名:%s\n", stu.name);

const char* gender = stu.getGender(&stu);

printf("性别:%s\n", gender);

printf("分数:%d\n", stu.mark);

这里有一个需要注意的地方,结构体声明后,结构体内的函数指针是无效的。必须先调用initStudent函数,将其设置正确的指向,才能使用这些函数指针。否则,将有可能导致程序崩溃。

为了让方法修改或访问对象,方法的参数中必须要有对象的指针。实现的形式中,第一个参数就是被操作对象指针。其它语言中,被操作对象指针是隐式传递的。不需要你在传参时写明参数,它会自动传入函数。例如,C++中会自动将一个名为this的对象指针作为方法的参数。而C语言中,无法做到自动将对象的指针传入方法,所以我们需要手动写上需要操作的对象的指针。

// C++的写法

stu.setGender("男");

// C语言的写法

stu.setGender(&stu, "男");

继承

除了学生之外,学校里面还需要有老师,老师也具有很多属性。例如:

  • 工号

  • 姓名

  • 性别

  • 任课科目

声明一个结构体用于表示老师。

struct teacher {

    int id; // 工号

    char name[20]; // 姓名

    int gender; // 性别

    char subject[20]; // 任课科目

};

比较一下学生和老师的结构体,看看它们之间有什么共同之处与不同之处。

struct teacher {

    int id; // 工号

    char name[20]; // 姓名

    int gender; // 性别

    char subject[20]; // 任课科目

};

struct student {

    int id; // 学号

    char name[20]; // 姓名

    int gender; // 性别

    int mark; // 分数

};

共同之处如下:

  • 编号

  • 姓名

  • 性别

不同之处:

  • 学生有考试分数

  • 老师有任课科目

我们可以把两个结构体中的共同之处抽象出来,让它共同之处成为一个新的结构。这个结构体具有老师和学生的共性,而老师与学生它们都是人,可以把这个结构体命名为person。

struct person {

    int id; // 编号

    char name[20]; // 姓名

    int gender; // 性别

};

接下来,我们可以让老师和学生结构包含这个person对象。

struct teacher {

    struct person super;

    char subject[20]; // 任课科目

};

struct student {

    struct person super;

    int mark; // 分数

};

让我们比较一下原有代码与现有代码。

原有代码:

// 原有代码

struct teacher {

    int id; // 工号

    char name[20]; // 姓名

    int gender; // 性别

    char subject[20]; // 任课科目

};

struct student {

    int id; // 学号

    char name[20]; // 姓名

    int gender; // 性别

    int mark; // 分数

};

现有代码

// 现有代码

struct person {

    int id; // 编号

    char name[20]; // 姓名

    int gender; // 性别

};

struct teacher {

    struct person super;

    char subject[20]; // 任课科目

};

struct student {

    struct person super;

    int mark; // 分数

};

原有代码中,老师和学生结构体中,均有id、name、gender三个变量。现有代码中,将这3个变量抽象成结构体person。这样一来,有两个好处:

  1. 减少重复代码

  2. 代码层次更清晰

由于student和teacher拥有person的一切,因此,我们可以说,student与teacher均继承于person。person是student与teacher的父对象。student与teacher是person的子对象。

刚刚我们只讨论了数据,现在我们结合上方法一起讨论。

struct person {

    int id; // 编号

    char name[20]; // 姓名

    int gender; // 性别

};

struct teacher {

    struct person super;

    char subject[20]; // 任课科目

};

struct student {

    struct person super;

    int mark; // 分数

    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);

    const char* (*getGender)(struct student* s);

    void (*setGender)(struct student* s, const char* strGender);

};

之前我们为student写了3个方法

  • 设置性别

  • 获取性别

  • 设置学号

其中,性别相关的方法也属于共性的方法。可以把这两个函数指针移动到person对象里面去,注意,要把方法的第一个参数struct student *修改为struct person *。移动后,子对象student与teacher均可以使用这一对性别相关的方法。而设置学号的方法,为student独有的方法,因此保持不变,依然将其放置在student对象内。

struct person {

    int id; // 编号

    char name[20]; // 姓名

    int gender; // 性别

    // 设置性别

    void (*setGender)(struct person* s, const char* strGender);

    // 获取性别

    const char* (*getGender)(struct person* s);

};

struct teacher {

    struct person super;

    char subject[20]; // 任课科目

};

struct student {

    struct person super;

    int mark; // 分数

    // 设置学号

    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);

};

对应上面的更改,函数getGender与setGender的第一个参数也要由struct student *修改为struct person *。

const char* getGender(struct person* p)

{

    if (p->gender == 0)

    {

        return "女";

    }

    else if (p->gender == 1)

    {

        return "男";

    }

    return "未知";

}

void setGender(struct person* p, const char* strGender)

{

    int numGender;

    if (strcmp("男", strGender) == 0)

    {

        numGender = 1;

    }

    else if (strcmp("女", strGender) == 0)

    {

        numGender = 0;

    }

    else

    {

        numGender = -1;

    }

    p->gender = numGender;

}

此外,setStudentId函数中,id成员,不在student中,而是在student中的person中。这里也要对应的修改一下。

void setStudentId(struct student* s, int year, int classNum, int serialNum)

{

    char buffer[20];

    sprintf(buffer, "%d%d%d", year, classNum, serialNum);

    int id = atoi(buffer);

    s->super.id = id; // 由s->id = id 修改为 s->super.id = id

}

还有,别忘了给结构初始化函数指针。

void initPerson(struct person* p)

{

    p->getGender = getGender;

    p->setGender = setGender;

}

void initStudent(struct student* s)

{

    initPerson(&(s->super));

    s->setStudentId = setStudentId;

}

void initTeacher(struct teacher* t)

{

    initPerson(&(t->super));

}

下面我们即可使用这些对象了。

struct student stu;

// 初始化student

initStudent(&stu);

// 学号:202212326

// 姓名:小明

// 性别: 男

// 分数:98

stu.setStudentId(&stu, 2022, 123, 26);

strcpy(stu.super.name, "小明");

stu.super.setGender(&stu.super, "男");

stu.mark = 98;

// 打印这些数值

printf("学号:%d\n", stu.super.id);

printf("姓名:%s\n", stu.super.name);

const char* gender = stu.super.getGender(&stu.super);

printf("性别:%s\n", gender);

printf("分数:%d\n", stu.mark);

putchar('\n');

struct teacher t;

// 初始化teacher

initTeacher(&t);

// 工号:12345

// 姓名:林老师

// 性别: 男

// 科目:C语言

t.super.id = 12345;

strcpy(t.super.name, "林老师");

t.super.setGender(&t.super, "男");

strcpy(t.subject, "C语言");

// 打印这些数值

printf("学号:%d\n", t.super.id);

printf("姓名:%s\n", t.super.name);

gender = t.super.getGender(&t.super);

printf("性别:%s\n", gender);

printf("科目:%s\n", t.subject);

多态

我们以绘制各种图形为背景,展开对多态这一特性的讨论。

绘制图形

现在,我们有3种图形,它们分别为:

  • 矩形

  • 圆形

  • 三角形

我们把这3种图形均看做对象,这些图形对象,分别需要有哪些属性呢?

  • 矩形:左上角坐标、右下角坐标

  • 圆形:圆心x坐标、圆心y坐标、半径

  • 三角形:三个顶点坐标

现在,我们用代码分别实现这几个对象。

struct Rect {

    int left;

    int top;

    int right;

    int bottom;

};

struct Circle {

    int x;

    int y;

    int r;

};

struct Triangle {

    POINT p1;

    POINT p2;

    POINT p3;

};

为了能够在屏幕上绘制这些图形,每个图形都设置一个名为draw的方法。

struct Rect {

    void (*draw)(struct Rect*);

    int left;

    int top;

    int right;

    int bottom;

};

struct Circle {

    void (*draw)(struct Circle*);

    int x;

    int y;

    int r;

};

struct Triangle {

    void (*draw)(struct Triangle*);

    POINT p1;

    POINT p2;

    POINT p3;

};

分别实现3个不同的绘制函数。

绘制矩形:

调用 easyx 中的 rectangle 函数,传入左上角坐标与右下角坐标。

void drawRect(struct Rect* r)

{

    rectangle(r->left, r->top, r->right, r->bottom);

}

绘制圆形:

调用 easyx 中的 circle 函数,传入圆心坐标与半径。

void drawCircle(struct Circle* c)

{

    circle(c->x, c->y, c->r);

}

绘制三角形:

调用 easyx 中的 line 函数,分别绘制点 p1 到 p2 的线段, p2 到 p3 的线段,以及 p3 到 p1 的线段。

void drawTriangle(struct Triangle* t)

{

    line(t->p1.x, t->p1.y, t->p2.x, t->p2.y);

    line(t->p2.x, t->p2.y, t->p3.x, t->p3.y);

    line(t->p3.x, t->p3.y, t->p1.x, t->p1.y);

}

下面,分别写3个初始化函数,用于给对象中的函数指针draw进行赋值。

void initRect(struct Rect* r)

{

    r->draw = drawRect;

}

void initCircle(struct Circle* r)

{

    r->draw = drawCircle;

}

void initTriangle(struct Triangle* r)

{

    r->draw = drawTriangle;

}



现在,准备工作都做好了,我们开始绘制这些图形吧。

int main()

{

    initgraph(800, 600);

    setaspectratio(1, -1);

    setorigin(400, 300);

    setbkcolor(WHITE);

    setlinecolor(BLACK);

    cleardevice();

    struct Rect r = { -200, 200, 200, 0 };

    struct Circle c = { 0, 0, 100 };

    struct Triangle t = { {0, 200}, {-200, 0}, {200, 0} };

    initRect(&r);

    initCircle(&c);

    initTriangle(&t);

    r.draw(&r);

    c.draw(&c);

    t.draw(&t);

    getchar();

    closegraph();

    return 0;

}

创建一个800 * 600的绘图窗体,设置x轴正方向为从左到右,y轴正方向为从下到上。将原点坐标从窗体左上角更改为窗体中心。设置背景颜色为白色,描边颜色为黑色,并使用背景色刷新整个窗体。下面分别声明矩形、圆形、三角形三个对象,并将需要的属性初始化。之后,三个对象分别调用各自的init函数,为对象内的函数指针赋值。完成准备工作后,即可使用对象 + 点 + 方法的形式,调用各自的draw方法绘制图形了。

多态

struct Rect {

    void (*draw)(struct Rect*);

    int left;

    int top;

    int right;

    int bottom;

};

struct Circle {

    void (*draw)(struct Circle*);

    int x;

    int y;

    int r;

};

struct Triangle {

    void (*draw)(struct Triangle*);

    POINT p1;

    POINT p2;

    POINT p3;

};

我们仔细观察这3个对象,看看它们分别有什么共性?可以发现,这3个对象,它们都有一个draw方法。那么,我们可以将draw这个方法抽象出来,单独放置到一个对象当中。由于这三个对象都是形状。我们可以把单独抽象出来的对象,命名为shape。shape对象中的draw方法,应当是一个共性的方法,所以,它的参数应当设置为struct Shape *。

struct Shape {

    void (*draw)(struct Shape*);

};

接下来,让Rect、Circle、Triangle三个对象分别都包含Shape对象。这样,它们就都能使用draw这个方法了。

struct Rect {

    struct Shape super;

    int left;

    int top;

    int right;

    int bottom;

};

struct Circle {

    struct Shape super;

    int x;

    int y;

    int r;

};

struct Triangle {

    struct Shape super;

    POINT p1;

    POINT p2;

    POINT p3;

};

这里有一个需要注意的地方,父对象与子对象的内存排布必须重合。

例如:下图中,上面的两个对象内存排布可以重合。而下面的两个对象的内存排布无法重合。

像下面一样的声明Rect是正确的。

// 正确

struct Rect {

    struct Shape super;

    int left;

    int top;

    int right;

    int bottom;

};

而下面一样的声明Rect是错误的。

// 错误

struct Rect {

    int left;

    int top;

    int right;

    int bottom;

    struct Shape super;

};

接着,我们需要修改各对象的初始化函数。将原有的r->draw改为r->super.draw。

void initRect(struct Rect* r)

{

    r->super.draw = drawRect;

}

void initCircle(struct Circle* c)

{

    c->super.draw = drawCircle;

}

void initTriangle(struct Triangle* t)

{

    t->super.draw = drawTriangle;

}

注意,这里还有一个问题,函数内赋值运算符左边的函数指针r->super.draw的类型为void (*)(struct Shape*),参数为struct Shape*。而赋值运算符右边的函数指针类型分别为:

void (*)(struct Rect*)

void (*)(struct Circle*)

void (*)(struct Triangle*)

函数指针参数类型不一致,无法进行赋值。我们可以把右边的函数指针强制类型转换为void (*)(struct Shape*)。

void initRect(struct Rect* r)

{

    r->super.draw = (void (*)(struct Shape*))drawRect;

}

void initCircle(struct Circle* c)

{

    c->super.draw = (void (*)(struct Shape*))drawCircle;

}

void initTriangle(struct Triangle* t)

{

    t->super.draw = (void (*)(struct Shape*))drawTriangle;

}

我们考虑一下怎样来使用这些对象。

struct Rect r = { {}, - 200, 200, 200, 0 };

struct Circle c = { {}, 0, 0, 100 };

struct Triangle t = { {}, {0, 200}, {-200, 0}, {200, 0} };

首先,声明Rect、Circle、Triangle这3个对象,并使用初始化列表将其初始化。注意,由于它们的第一个成员为super,所以,这里使用空列表{},将super成员初始化为零。

initRect(&r);

initCircle(&c);

initTriangle(&t);

让三个对象分别调用各自的初始化函数,给各自对象super成员中的draw设置为各自对应的绘图函数。

  • r.super.draw设置为drawRect

  • c.super.draw设置为drawCircle

  • t.super.draw设置为drawRTriangle

struct Shape* arrShape[3] = {

(struct Shape*)&r, (struct Shape*)&c, (struct Shape*)&t };

声明一个元素类型为struct Shape *的数组,元素个数为3。分别用r的指针,c的指针,t的指针初始化。注意,这里也需要进行强制类型转换,否则初始化列表里面的指针类型和数组元素的指针类型不一致。

for (int i = 0; i < 3; i++)

{

    arrShape[i]->draw(arrShape[i]);

}

到了关键的一步,使用循环,依次调用draw函数。由于3次循环中的draw函数分别为各个图形各自的绘图函数。所以,虽然统一调用的是draw,但是,却可以执行它们各自的绘图函数。至此,不同实现的方法,在此得到统一。

总结实现多态的步骤

  1. 抽离出各个对象中共有的方法draw,将其单独放置在一个对象Shape内。

  2. 各个对象均继承于Shape对象。

  3. 将各个子对象中的draw方法,设置为各自的实现方法。

  4. 声明一个Shape对象的指针,并将其赋值为一个子对象的指针。

  5. 通过上述对象指针,调用方法共有方法draw,执行的是第三步中设置的方法。

标签:struct,stu,int,void,C语言,面向对象,student,super
From: https://blog.csdn.net/sycc512/article/details/141916416

相关文章

  • C语言学习——sprintf函数详细解释及其用法
    文章目录函数功能:把格式化的数据写入某个字符串参数说明及应用举例解释:连接字符串打印地址信息利用sprintf的返回值使用sprintf的常见问题函数功能:把格式化的数据写入某个字符串头文件:stdio.h函数原型:intsprintf(char*buffer,constchar*format,[arg......
  • PHP8面向对象快速入门五 接口 抽象类
    在PHP中,接口是一种定义方法但不实现它们的方式。接口可以被类实现,使得这些类承诺实现接口中定义的所有方法。接口主要用于定义类的共同行为,而不涉及具体的实现细节。以下是PHP接口的基本用法:定义接口interfaceAnimal{publicfunctionmakeSound();publicfunct......
  • 学习C语言结构体(结构体的前世今生)
    1、首先我将使用DevC++这个软件(其实随意一个C++软件都可以)来演示一下结构体的使用方法。这里已经写了一个最简单的HelloWorld!程序。2、对于C语言的数据来说最重要的就是两个功能,一个是定义数据,一个是引用数据。既然结构体也是数据类型,那么他就和其他的数据类型差不多。也分......
  • 20240906_142048 c语言 认识c语言
    C语言是一种广泛使用的编程语言,它以其高效、灵活和接近硬件的特性而闻名。对于零基础的学生来说,学习C语言是一个很好的起点,因为它不仅能帮助你理解计算机程序的基本结构和概念,还能为学习更高级的编程语言(如C++、Java、Python等)打下坚实的基础。下面我将简要介绍C语言的一些基本概念......
  • C语言之动态内存分配与释放
    C语言之动态内存分配与释放通用指针类型void通用类型指针具有以下特点:类型无关,赋值灵活:由于指针本质上是一个存储内存地址的变量,而内存地址是没有类型的,所以void指针可以存储任意类型数据的地址,指向任意类型对象。无论是整数、浮点数、字符或数组、结构体等类型都可以用void指......
  • 新手c语言讲解及题目分享(十九)--数据类型专项练习
    本文主要讲解c语言的基础部分,常见的c语言基础数据类型,这个也非常重要。参考书目和推荐学习书目:通过网盘分享的文件:C语言程序设计电子教材(1).pdf链接:https://pan.baidu.com/s/1JFqSaCKZ0A2Lr944e72NUA?pwd=p648提取码:p648目录前言一.常量与变量1.常量2.变量二.......
  • 新手c语言讲解及题目分享(十八)--基本输入输出函数专项练习
    本文主要讲解c语言的基础部分,基本的输入与输出,通过手动的输入从而得到自己想要的预期值。参考书目和推荐学习书目:通过网盘分享的文件:C语言程序设计电子教材(1).pdf链接:https://pan.baidu.com/s/1JFqSaCKZ0A2Lr944e72NUA?pwd=p648提取码:p648目录前言一.格式输出......
  • 14 Python面向对象编程:反射
    本篇是Python系列教程第14篇,更多内容敬请访问我的Python合集在Python中,“反射”通常指的是根据字符串查找并执行相关的类、方法或者属性的能力。Python提供了几个内置函数和语法特性来支持这种能力,比如getattr(),setattr(),hasattr(),delattr()以及dir()等。这些功能......
  • C语言 10 数组
    简单来说,数组就是存放数据的一个组,所有的数据都统一存放在这一个组中,一个数组可以同时存放多个数据。一维数组比如现在想保存12个月的天数,那么只需要创建一个int类型的数组就可以了,它可以保存很多个int类型的数据,这些保存在数组中的数据,称为元素://12个月的数据全部保存......
  • Python中的“类的属性与方法”:解锁面向对象编程的奥秘
    在这个充满数据的世界里,Python作为一门强大的编程语言,已经成为了许多开发者的首选工具。而在Python中,类的属性与方法则是构建复杂系统不可或缺的一部分。它们不仅能够帮助我们更好地组织代码,还能提高程序的可读性和维护性。今天,就让我们一起探索类的属性与方法的魅力所在,从基础到进......