当前位置: 首页 > news >正文

C 语言面向对象

面向对象的基本特性:封装,继承,多态


1.0 面向过程概念


当我们在编写程序时,通常采用以下步骤:
1. 将问题的解法分解成若干步骤
2. 使用函数分别实现这些步骤
3. 依次调用这些函数

这种编程风格的被称作 面向过程 。除了 面向过程 之外,还有一种被称作 面向对象 的编程风格被广泛使 用。
面向对象 采用基于对象的概念建立模型,对现实世界进行模拟,从而完成对问题的解决。
C 语言的语法并不直接支持面向对象风格的编程。但是,我们可以通过额外的代码,让 C 语言实现一些面向对象特性。

2.0 程序案例


#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>struct student
{int id;char name[20];int gender;int mark;
};int MakeStudentId(int year, int classNum, int serialNum)
{// 创建一个char类型的数组char buffer[20];// 将三个变量转换为指定格式的字符串,存储在buffer数组中sprintf(buffer, "%d%d%d", year, classNum, serialNum);// 将字符串转换为整数int id = atoi(buffer);// 返回项目的id值return id;
}const char* NumGenderToStrGender(int numGender)
{if (numGender == 0){return "女";}else if (numGender == 1){return "男";}return "NULL";
}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;
}int main()
{// 创建结构体变量struct student stu;stu.id = MakeStudentId(2024, 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("分数:%s\n", stu.mark);return 0;}
现在,我们使用 面向过程 风格写了 3 个函数和一个结构体,并且调用了这些函数,将函数返回的结果赋值给了结构体。接下来,让我们以面向对象风格来重新审视这段代码。

3.0 面向对象

现在,我们使用 面向过程 风格写了 3 个函数和一个结构体,并且调用了这些函数,将函数返回的结果赋值给了结构体。接下来,让我们以面向对象风格来重新审视这段代码。
在面向对象风格中,结构体被看做 数据( data ,而操作数据的函数称作 方法( method 。目前函数和数据是分离的,函数并不直接操作数据,我们需要拿到函数返回的结果,再将其赋值给数据。
面向对 象风格编程的第一大特性 --- 封装 ,它希望 方法直接操作数据 ,并且将数据和方法 结合 在一起,它们构成 一个整体, 而这个整体被称作 对象
此外,还有一个方法命名上的规则。一般来说, 获取数据的方法会被命名为 getXXX ,设置数据的方法 会被命名为 setXXX 。

成员id的表示方式:


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

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

4.0 封装特性


我们来看看学校里面最重要的主体是什么?是学生,学生肯定拥有很多属性,比如学生的学号、姓名、性别、考试分数等等。自然地,我们会声明一个结构体用于表示学生。
typedef struct
{int id;char name[20];int gender;int mark;
}StudentInfo_t;

注:将需要的信息封装为一个结构体内部包含学生的姓名,学号,性别,分数。


通过函数设置学生的id编号,函数可以通过结构体指针,直接操作结构体中的数据

void SetStudentId(StudentInfo_t* 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;
}

获取学生的性别函数,在面向对象的编程方法中获取数据的函数被我们设置为GetXXX,设置数据的函数被我们设置为SetXXX。

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

设置数据的方法

void SetGender(StudentInfo_t* 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;
}

通过主函数进行调用

int main()
{StudentInfo_t stu;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);
}

完整函数代码

#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>typedef struct
{int id;char name[20];int gender;int mark;
}StudentInfo_t;void SetStudentId(StudentInfo_t* 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(StudentInfo_t* s)
{if (s->gender == 0){return "女";}else if (s->gender == 1){return "男";}return "未知";
}void SetGender(StudentInfo_t* 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;
}int main()
{StudentInfo_t stu;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个函数的函数指针。

5.0 面向对象

在结构体中声明函数指针

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; // 分数
};

这个时候可以将结构体作为一个对象看待,使用对象(结构体变量). 方法(函数)的方式进行参数的赋值和调用。


生成学生的id

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;
}

初始化函数指针【对象初始化之后才能被调用】

void initStudent(struct student* s)
{s->setStudentId = setStudentId;s->getGender = getGender;s->setGender = setGender;
}

主函数相关代码

int main()
{struct student stu;// 初始化studentinitStudent(&stu);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);
}

完整代码

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>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; // 分数
};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;
}void initStudent(struct student* s)
{s->setStudentId = setStudentId;s->getGender = getGender;s->setGender = setGender;
}int main()
{struct student stu;// 初始化studentinitStudent(&stu);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);
}

6.0 继承基本概念


除了学生之外,学校里面还需要有老师,老师也具有很多属性。例如:
  1. 工号
  2. 姓名
  3. 性别
  4. 任课科目
声明一个结构体用于表示老师。
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; // 分数
};
共同之处如下:
  1. 编号
  2. 姓名
  3. 性别
不同之处:
  1. 学生有考试分数
  2. 老师有任课科目
我们可以把两个结构体中的共同之处 抽象 出来,让它共同之处成为一个新的结构。这个结构体具有老师 和学生的共性,而老师与学生它们都是人,可以把这个结构体命名为 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 对象内。

创建一个Person方法,内部包含

struct person 
{int id;char name[20];int gender;const char* (*getGender)(struct student* s);void (*setGender)(struct student* s, const char* strGender);
};struct teacher 
{// 创建结构体成员变量struct person super;char subject[20];
};struct student {void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);struct person super;int mark; // 分数
};
对应上面的更改,函数 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;
}
还有,别忘了给结构初始化函数指针。
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));
}

main函数调用

int main()
{struct student stu;// 初始化studentinitStudent(&stu);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;// 初始化teacherinitTeacher(&t);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);
}

完整代码

#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>struct person 
{int id;char name[20];int gender;const char* (*getGender)(struct student* s);void (*setGender)(struct student* s, const char* strGender);
};struct teacher 
{// 创建结构体成员变量struct person super;char subject[20];
};struct student {void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);struct person super;int mark; // 分数
};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;
}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;
}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));
}int main()
{struct student stu;// 初始化studentinitStudent(&stu);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;// 初始化teacherinitTeacher(&t);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);
}

程序运行结果


7.0 多态


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 ,但是,却可以执行它们各自的绘图函数。至此,不同实现 的方法,在此得到统一。

完整代码实现

#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <easyx.h>
#include <stdlib.h>struct Shape
{void (*draw)(struct Shape*);
};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;
};void drawRect(struct Rect* r) 
{rectangle(r->left, r->top, r->right, r->bottom);
}void drawCircle(struct Circle* c) 
{circle(c->x, c->y, c->r);
}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);
}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;
}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);struct Shape* arrShape[3] ={(struct Shape*)&r,(struct Shape*)&c,(struct Shape*)&t};for (int i = 0; i < 3; i++) {arrShape[i]->draw(arrShape[i]);}getchar();closegraph();return 0;
}

让我们回顾一下在之前实现多态的步骤:
1. 抽离出各个对象中共有的方法 draw ,将其单独放置在一个对象 Shape 内。
2. 各个对象均继承于 Shape 对象。
3. 将各个子对象中的 draw 方法,设置为各自的实现方法。
4. 声明一个 Shape 对象的指针,并将其赋值为一个子对象的指针。
5. 通过上述对象指针,调用方法共有方法 draw ,执行的是第三步中设置的方法。

注:参考你好编程C语言教程编写,仅供学习参考

相关文章:

C 语言面向对象

面向对象的基本特性&#xff1a;封装&#xff0c;继承&#xff0c;多态 1.0 面向过程概念 当我们在编写程序时&#xff0c;通常采用以下步骤&#xff1a; 1. 将问题的解法分解成若干步骤 2. 使用函数分别实现这些步骤 3. 依次调用这些函数 这种编程风格的被称作 面向过程…...

初试无监督学习 - K均值聚类算法

文章目录 1. K均值聚类算法概述2. k均值聚类算法演示2.1 准备工作2.2 生成聚类用的样本数据集2.3 初始化KMeans模型对象&#xff0c;并指定类别数量2.4 用样本数据训练模型2.5 用训练好的模型生成预测结果2.6 输出预测结果2.7 可视化预测结果 3. 实战小结 1. K均值聚类算法概述…...

部署实战(二)--修改jar中的文件并重新打包成jar文件

一.jar文件 JAR 文件就是 Java Archive &#xff08; Java 档案文件&#xff09;&#xff0c;它是 Java 的一种文档格式JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中&#xff0c;多出了一个META-INF/MANIFEST.MF 文件META-INF/MANIFEST.MF 文件在生成 JAR 文件的时候…...

C++中的原子操作:原子性、内存顺序、性能优化与原子变量赋值

一、原子操作与原子性 原子操作&#xff08;atomic operation&#xff09;是并发编程中的一个核心概念&#xff0c;指的是在多线程环境中&#xff0c;一个操作一旦开始&#xff0c;就不会被其他线程的操作打断&#xff0c;直至该操作完成。这种不可分割的特性保证了操作的原子…...

【JavaEE初阶】多线程初阶下部

文章目录 前言一、volatile关键字volatile 能保证内存可见性 二、wait 和 notify2.1 wait()方法2.2 notify()方法2.3 notifyAll()方法2.4 wait 和 sleep 的对比&#xff08;面试题&#xff09; 三、多线程案例单例模式 四、总结-保证线程安全的思路五、对比线程和进程总结 前言…...

数据结构(Java版)第二期:包装类和泛型

目录 一、包装类 1.1. 基本类型和对应的包装类 1.2. 装箱和拆箱 1.3. 自动装箱和自动拆箱 二、泛型的概念 三、引出泛型 3.1. 语法规则 3.2. 泛型的优点 四、类型擦除 4.1. 擦除的机制 五、泛型的上界 5.1. 泛型的上界的定义 5.2. 语法规则 六、泛型方法 6.1…...

[原创](Modern C++)现在C++的关键性概念: 通俗易懂的解释“多态“与“虚函数“的内在关系

常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共23年] 职业生涯: 21年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi、XCode、Eclipse、C Bui…...

python操作Elasticsearch

使用elasticsearch 6.x版本&#xff0c;操作es数据。 #! -*- coding:utf-8 -* import timefrom elasticsearch import Elasticsearch, helpersclass EstUtil:_instance Nonedef __new__(cls, *args, **kwargs):if not cls._instance:cls._instance super(EstUtil, cls).__ne…...

【数据分享】2024年我国省市县三级的住宿服务设施数量(8类住宿设施/Excel/Shp格式)

宾馆酒店、旅馆招待所等住宿服务设施的配置情况是一个城市公共基础设施完善程度的重要体现&#xff0c;一个城市住宿服务设施种类越丰富&#xff0c;数量越多&#xff0c;通常能表示这个城市的公共服务水平越高&#xff01; 本次我们为大家带来的是我国各省份、各地级市、各区…...

从尾到头打印链表 剑指offer

题目描述 输入一个链表的头节点&#xff0c;从尾到头反过来打印出每个节点的值。 链表节点定义如下&#xff1a; struct ListNode {int m_nKey;ListNode*m_pNext; }; 代码实现 栈实现&#xff1a; 递归实现&#xff1a; 但是用递归实现可能存在的问题&#xff1a;...

【测试工具JMeter篇】JMeter性能测试入门级教程(二)出炉,测试君请各位收藏了!!!

上篇文章&#xff1a;CSDN 我们介绍了JMeter的一些原理介绍&#xff0c;以及安装配置和启动流程&#xff0c;本文我们就来讲讲JMeter如何使用。 一、JMeter目录结构组成 1. 根目录 Jmeter安装包解压后的根目录如下图&#xff1a; 1.1 backups目录&#xff1a;脚本备份目录&am…...

【JavaEE】Servlet:表白墙

文章目录 一、前端二、前置知识三、代码1、后端2、前端3、总结 四、存入数据库1、引入 mysql 的依赖&#xff0c;mysql 驱动包2、创建数据库数据表3、调整上述后端代码3.1 封装数据库操作&#xff0c;和数据库建立连接3.2 调整后端代码 一、前端 <!DOCTYPE html> <ht…...

WPF中如何让Textbox显示为一条直线

由于Textbox直接使用是一条直线 设置如下代码 可以让Textbox变为直线输入 <Style TargetType"TextBox"x:Key"UsernameTextBoxStyle"><Setter Property"Template"><Setter.Value><ControlTemplate TargetType"{x:Typ…...

多维数组与特殊矩阵:存储与压缩

多维数组与特殊矩阵&#xff1a;存储与压缩 一、多维数组的存储 &#xff08;一&#xff09;基本概念 多维数组是线性表的推广&#xff0c;例如二维数组可以看作是元素为一维数组的线性表&#xff0c;三维数组可以看作是元素为二维数组的线性表&#xff0c;以此类推。在内存…...

第三讲 架构详解:“隐语”可信隐私计算开源框架

目录 隐语架构 隐语架构拆解 产品层 算法层 计算层 资源层 互联互通 跨域管控 本文主要是记录参加隐语开源社区推出的第四期隐私计算实训营学习到的相关内容。 隐语架构 隐语架构拆解 产品层 产品定位&#xff1a; 通过可视化产品&#xff0c;降低终端用户的体验和演…...

springboot项目使用maven打包,第三方jar问题

springboot项目使用maven package打包为可执行jar后&#xff0c;第三方jar会被打包进去吗&#xff1f; 答案是肯定的。做了实验如下&#xff1a; 第三方jar的项目结构及jar包结构如下&#xff1a;&#xff08;该第三方jar采用的是maven工程&#xff0c;打包为普通jar&#xf…...

突破内存限制:Mac Mini M2 服务器化实践指南

本篇文章&#xff0c;我们聊聊如何使用 Mac Mini M2 来实现比上篇文章性价比更高的内存服务器使用&#xff0c;分享背后的一些小的思考。 希望对有类似需求的你有帮助。 写在前面 在上文《ThinkPad Redis&#xff1a;构建亿级数据毫秒级查询的平民方案》中&#xff0c;我们…...

【Linux】Linux系统电源状态

前言 本文主要介绍Linux系统电源状态。 Linux内核代码声明如下&#xff0c;位于kernel/power/suspend.c。 参考链接 Linux系统电源状态 在Linux操作系统中&#xff0c;将电源划分为如下几个状态&#xff1a; ACPI StateLinux StateDescriptionS0On(on)WorkingS1Standby(sta…...

《用Python画蔡徐坤:艺术与编程的结合》

简介 大家好&#xff01;今天带来一篇有趣的Python编程项目&#xff0c;用代码画出知名偶像蔡徐坤的形象。这个项目使用了Python的turtle库&#xff0c;通过简单的几何图形和精心设计的代码来展示艺术与编程的结合。 以下是完整的代码和效果介绍&#xff0c;快来试试看吧&…...

ARM(安谋) China处理器

0 Preface/Foreword 0.1 参考博客 Cortex-M23/M33与STAR-MC1星辰处理器 ARM China&#xff0c;2018年4月established&#xff0c;独立运行。 1 处理器类型 1.1 周易AIPU 1.2 STAR-MC1&#xff08;星辰处理器&#xff09; STAT-MC1&#xff0c;主要为满足AIOT应用性能、功…...

硬中断关闭后的堆栈抓取方法

一、背景 性能和稳定性是一个计算机工程里的一个永恒的主题。其中尤其稳定性这块的问题发现和问题分析及问题解决就依赖合适的对系统的观测的手段&#xff0c;帮助我们发现问题&#xff0c;识别问题原因最后才能解决问题。稳定性问题里尤其底层问题里&#xff0c;除了panic问题…...

电影风格城市夜景旅拍Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 电影风格城市夜景旅拍通过 Lightroom 调色&#xff0c;将城市夜晚的景色打造出如同电影画面般的质感和氛围。以独特的色彩和光影处理&#xff0c;展现出城市夜景的魅力与神秘。 预设信息 调色风格&#xff1a;电影风格预设适合类型&#xff1a;人像&#xff0c;街拍…...

基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功

基于FPGA的2FSK调制 前言一、2FSK储备知识二、代码分析1.模块分析2.波形分析 总结 前言 设计实现连续相位 2FSK 调制器&#xff0c;2FSK 的两个频率为:fI15KHz&#xff0c;f23KHz&#xff0c;波特率为 1500 bps,比特0映射为f 载波&#xff0c;比特1映射为 载波。 1&#xff09…...

【Python】构建事件驱动架构:用Python实现实时应用的高效系统

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 事件驱动架构(Event-Driven Architecture,EDA)是一种基于事件流动进行系统设计的模式,广泛应用于游戏开发、实时监控和分布式系统中。它通过解耦事件的生产者和消费者,提升系统的可扩展性和灵活性。本文章从…...

安装 Docker(使用国内源)

一、安装Docker-ce 1、下载阿里云的repo源 [rootlocalhost ~]# yum install yum-utils -y && yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo && yum makecache # 尝试列出 docker-ce 的版本 [rootlocalh…...

001 MATLAB介绍

前言&#xff1a; 软件获取渠道有很多&#xff0c;难点也就是百度网盘下载慢&#xff1b; 线上版本每月有时间限制。 01 MATLAB介绍 性质&#xff1a; MATLAB即Matrix Laboratory 矩阵实验室的意思&#xff0c;是功能强大的计算机高级语言, 已广泛应用于各学科研究部门、…...

vscode利用ofExtensions插件可以调试单进程Openfoam,但是不能调试mpi多进程案例

问题&#xff1a; 准备调试流固耦合案例&#xff0c;包括流体和固体的&#xff0c;但是用ofextensions插件。但是流体的话使用的是域分解方法&#xff0c;将大的单元分成了小的单元用mpi并行处理&#xff0c;里面的program必须输入"/usr/bin/mpirun", // 这里改为使…...

2022年计算机网络408考研真题解析

第一题&#xff1a; 解析&#xff1a;网络体系结构-数据链路层 在ISO网络参考模型中&#xff0c;运输层&#xff0c;网络层和数据链路层都实现了流量的控制功能&#xff0c;其中运输层实现的是端到端的流量控制&#xff0c;网络层实现的是整个网络的流量控制&#xff0c;数据链…...

React-useEffect的使用

useEffect react提供的一个常用hook&#xff0c;用于在函数组件中执行副作用操作&#xff0c;比如数据获取、订阅或手动更改DOM。 基本用法&#xff1a; 接受2个参数&#xff1a; 一个包含命令式代码的函数&#xff08;副作用函数&#xff09;。一个依赖项数组&#xff0c;用…...

python学习笔记(10)算法(3)列表

一、列表 列表&#xff08;list&#xff09;是一个抽象的数据结构概念&#xff0c;它表示元素的有序集合&#xff0c;支持元素访问、修改、添加、删除和遍历 等操作&#xff0c;无须使用者考虑容量限制的问题。列表可以基于链表或数组实现。 ‧ 链表天然可以看作一个列表&#…...

嵌入式系统与单片机工作原理详解

随着现代科技的发展&#xff0c;嵌入式系统已经深入到我们日常生活中的方方面面。无论是智能家居、汽车电子&#xff0c;还是工业控制、医疗设备&#xff0c;都离不开嵌入式系统的支持。而单片机作为嵌入式系统的核心组件&#xff0c;是实现这些功能的关键之一。本文将详细介绍…...

Spark SQL 之 QueryStage

ExchangeQueryStageExec ExchangeQueryStageExec 分为两种...

Flink Standalone 集群模式安装部署教程

目录 一、前言 二、环境准备 三、安装步骤 1. 下载并安装 Flink 4. 配置 Flink 5. 配置环境变量 6. 启动 Flink 集群 7. 访问 Flink Web 界面 四、简单测试 五、常见问题和解决办法 1. 启动失败&#xff0c;无法连接到 TaskManager 2. Web 界面无法访问 六、总结 …...

【运维】 使用 shell 脚本实现类似 jumpserver 效果实现远程登录linux 服务器

实现效果 通过序号选择登录&#xff1a; 配置证书登录 配置证书登录可以免去每次都输入密码的麻烦。详见另一篇博文&#xff1a; 【ssh】使用秘钥对&#xff08;公钥/私钥&#xff09;登录linux主机以及原理介绍 自动登录脚本 直接复用以下脚本即可&#xff0c;在 server…...

根据实验试要求,打通隧道连接服务器上的数据库,前端进行数据调用。

1.背景介绍 数据库布置在了工大实验试K80服务器上&#xff0c;本地属于外网无法直接访问校园内网。需要打通隧道&#xff0c;通过堡垒机进行服务器的访问。获取到数据库数据进行前端展示。 2.打通隧道 访问指令&#xff1a; 我选择使用Xshell打通隧道。优点&#xff1a;凭证…...

ubuntu 安装 docker 记录

本文假设系统为 Ubuntu&#xff0c;从 16.04 到 24.04&#xff0c;且通过 APT 命令安装。理论上也其他 Debian 系的操作系统。 WSL 也一样。 感觉 Docker 官方在强推 Docker Desktop&#xff0c;搜索 Docker 安装文档&#xff0c;一不小心就被导航到了 Docker Desktop 的安装页…...

46.坑王驾到第十期:vscode 无法使用 tsc 命令

点赞收藏加关注&#xff0c;你也能住大别墅&#xff01; 一、问题重现 上一篇帖子记录了我昨天在mac上安装typescript及调试的过程。今天打开vscode准备开干的时候&#xff0c;发现tsc命令又无法使用了&#xff0c;然后按照昨天的方法重新安装调试后又能用了&#xff0c;但是关…...

pytorch3d linux安装

目录 测试成功2024.11.21&#xff1a; 测试成功2024.11.21&#xff1a; python3.10 GitHub - facebookresearch/pytorch3d: PyTorch3D is FAIRs library of reusable components for deep learning with 3D data 安装脚本&#xff1a; cd pytorch3d && pip install…...

P1168 中位数

网址如下&#xff1a;P1168 中位数 - 洛谷 | 计算机科学教育新生态 一道求中位数的题&#xff0c;本来是想再用二分法来试一下的&#xff0c;但是出现了一点问题&#xff0c;先把AC的放出来 很简单&#xff0c;一个记录比中位数大的数的最小堆&#xff0c;和一个记录比中位数小…...

全面解析多种mfc140u.dll丢失的解决方法,五种方法详细解决

当你满心期待地打开某个常用软件&#xff0c;却突然弹出一个错误框&#xff0c;提示“mfc140u.dll丢失”&#xff0c;那一刻&#xff0c;你的好心情可能瞬间消失。这种情况在很多电脑用户的使用过程中都可能出现。无论是游戏玩家还是办公族&#xff0c;面对这个问题都可能不知所…...

✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本

api.js //封装ajax方法 import $g from "../sg";//vue项目使用 import $ from jquery;//(提示&#xff1a;原生开发页面请前往https://jquery.com下载最新版jQuery) import { Message } from "element-ui";//element项目使用 // import axios from "…...

【Python】分割秘籍!掌握split()方法,让你的字符串处理轻松无敌!

在Python开发中&#xff0c;字符串处理是最常见也是最基础的任务之一。而在众多字符串操作方法中&#xff0c;split()函数无疑是最为重要和常用的一个。无论你是Python新手&#xff0c;还是经验丰富的开发者&#xff0c;深入理解并熟练运用split()方法&#xff0c;都将大大提升…...

非root用户安装CUDA

1.使用nvidia-smi查看当前驱动支持的最高CUDA版本&#xff1a; 表示当前驱动最多支持cuda12.1 2.进入cuda安装界面&#xff0c;https://developer.nvidia.com/cuda-toolkit-archive&#xff0c;选择想要安装的版本&#xff0c;例如想要安装CUDA11.4&#xff1a; 如果需要查看ub…...

Qt中2D绘制系统

目录 一、Qt绘制系统 1.1Qt绘制基本概念 1.2 绘制代码举例 1.3画家 1.3.1 QPainter的工作原理&#xff1a; 1.3.2 自定义绘制饼状图&#xff1a; 1.4画笔和画刷 1.4.1画笔 1.4.2 画刷填充样式 1.5 反走样和渐变 1.6绘制设备 1.7坐标变换 1.8QPainterPath 1.9绘制文…...

实战项目负载均衡式在线 OJ

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;能自己实现负载均衡式在线 OJ。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff1…...

shell脚本启动springboot项目

nohup java -jar springboot.jar > springboot.log 2>&1 & 表示日志输出重定向到springboot.log日志文件, 而原本的日志继续输出到 项目同级的log文件夹下, 所以这个重定向没必要. 我们没必要要2分日志 #!/bin/bash# 获取springboot项目的进程ID PID$(ps -e…...

Django 自定义路由转换器

步骤 创建自定义转换器类 必须定义 regex 属性&#xff08;用于匹配参数&#xff09;。必须实现 to_python 和 to_url 方法。 to_python: 将匹配的参数转换为视图函数可用的 Python 数据。to_url: 将数据转换为 URL 格式&#xff08;用于反向解析&#xff09;。 注册转换器 使用…...

低速接口项目之串口Uart开发(二)——FIFO实现串口数据的收发回环测试

本节目录 一、设计思路 二、loop环回模块 三、仿真模块 四、仿真验证 五、上板验证 六、往期文章链接本节内容 一、设计思路 串口数据的收发回环测试&#xff0c;最简单的硬件测试是把Tx和Rx连接在一起&#xff0c;然后上位机进行发送和接收测试&#xff0c;但是需要考虑到串…...

Oracle 数据库 IDENTITY 列

IDENTITY列是Oracle数据库12c推出的新特性。之所以叫IDENTITY列&#xff0c;是由于其支持ANSI SQL 关键字 IDENTITY&#xff0c;其内部实现还是使用SEQUENCE。 不过推出这个新语法也是应该的&#xff0c;毕竟MyQL已经有 AUTO_INCREMENT列&#xff0c;而SQL Server也已经有IDENT…...

算法笔记:单调队列

单调队列&#xff1a; 队列元素之间的关系具有单调性&#xff08;从队首到队尾单调递增/递减&#xff09;&#xff0c;队首和队尾都可以进行入队出队&#xff08;即插入删除&#xff09;操作&#xff0c;本质是由双端队列deque实现。 适用问题 通常解决动态小区间中寻找极值…...