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语言教程编写,仅供学习参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/62055.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VMware16安装macOS12【详细教程】

因为在应用上线IOS应用商店时&#xff0c;需要用到mac系统进行&#xff0c;于是就在VMware16pro虚拟机进行安装macOS12系统&#xff0c;安装的过程做了一个记录&#xff0c;希望对你有所帮助&#xff01; 前言 首先需要下载好下面工具&#xff1a; VMware workstation pro 16…

视频推拉流EasyDSS互联网直播点播平台技术特点及应用场景剖析

在数字科技日新月异的今天&#xff0c;视频直播和点播已经成为互联网内容传播的重要方式之一。而互联网直播点播平台EasyDSS作为功能强大的流媒体直播点播视频能力平台&#xff0c;提供了一站式的视频推拉流、转码、直播、点播、时移回放、存储等视频服务&#xff0c;广泛应用于…

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

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

从 Llama 1 到 3.1:Llama 模型架构演进详解

编者按&#xff1a; 面对 Llama 模型家族的持续更新&#xff0c;您是否想要了解它们之间的关键区别和实际性能表现&#xff1f;本文将探讨 Llama 系列模型的架构演变&#xff0c;梳理了 Llama 模型从 1.0 到 3.1 的完整演进历程&#xff0c;深入剖析了每个版本的技术创新&#…

【Qt】QComboBox设置默认显示为空

需求 使用QComboBox&#xff0c;遇到一个小需求是&#xff0c;想要设置未点击出下拉列表时&#xff0c;内容显示为空。并且不想在下拉列表中添加一个空条目。 实现 使用setPlaceholderText()接口。我们先来看下帮助文档&#xff1a; 这里说的是&#xff0c;placeholderText是…

mysql根据日期查询没有的日期也要显示数据

先查询出日期数据(当前日期往前推12个月) select bb.datefrom (select num : num 1,date_format(adddate(date_sub(date_sub(curdate(),interval 12 month),interval 1 month),interval num month), %Y-%m) as datefrom mysql.help_topic,(select num : 0) as twhere addd…

非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…

Halo 正式开源: 使用可穿戴设备进行开源健康追踪

在飞速发展的可穿戴技术领域&#xff0c;我们正处于一个十字路口——市场上充斥着各式时尚、功能丰富的设备&#xff0c;声称能够彻底改变我们对健康和健身的方式。 然而&#xff0c;在这些光鲜的外观和营销宣传背后&#xff0c;隐藏着一个令人担忧的现实&#xff1a;大多数这些…

Python 爬虫从入门到(不)入狱学习笔记

爬虫的流程&#xff1a;从入门到入狱 1 获取网页内容1.1 发送 HTTP 请求1.2 Python 的 Requests 库1.2 实战&#xff1a;豆瓣电影 scrape_douban.py 2 解析网页内容2.1 HTML 网页结构2.2 Python 的 Beautiful Soup 库 3 存储或分析数据&#xff08;略&#xff09; 一般爬虫的基…

黄仁勋:人形机器人在内,仅有三种机器人有望实现大规模生产

11月23日&#xff0c;芯片巨头、AI时代“卖铲人”和最大受益者、全球市值最高【英伟达】创始人兼CEO黄仁勋在香港科技大学被授予工程学荣誉博士学位&#xff1b;并与香港科技大学校董会主席沈向洋展开深刻对话&#xff0c;涉及人工智能&#xff08;AI&#xff09;、计算力、领导…

【Linux学习】【Ubuntu入门】2-3 make工具和makefile引入

1.使用命令新建三个.c文件vi main.c&#xff0c;vi input.c&#xff0c;vi caclcu.c&#xff0c;两个.h文件vi input.h&#xff0c;vi caclcu.h 2.vi Makefile&#xff1a;新建Makefile文件&#xff0c;输入一下内容 注意&#xff1a;命令列表中每条命令前用TAB键&#xff0c;不…

wsl2的Ubuntu18.04安装ros和anaconda

参考&#xff1a;超详细 WSL2 安装 ros 和 anaconda_wsl2安装anaconda-CSDN博客 一.安装ros 1. 更换系统源 输入 wget http://fishros.com/install -O fishros && . fishros 和上面的链接一样&#xff0c;依次输入5-2-1 2. 安装ros 输入 wget http://fishros.c…

1-golang_org_x_crypto_bcrypt测试 --go开源库测试

1.实例测试 package mainimport ("fmt""golang.org/x/crypto/bcrypt" )func main() {password : []byte("mysecretpassword")hashedPassword, err : bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)if err ! nil {fmt.Println(err)…

【FPGA】Verilog:利用 4 个串行输入- 串行输出的 D 触发器实现 Shift_register

0x00 什么是寄存器 寄存器(Register)是顺序逻辑电路中使用的基本组成部分之一。寄存器用于在数字系统中存储和处理数据。寄存器通常由位(bit)构成,每个位可以存储一个0或1的值。通过寄存器,可以设计出计数器、加法器等各种数据处理电路。 0x01 寄存器的种类 基于 D 触发…

用 Python 从零开始创建神经网络(十):优化器(Optimizers)(持续更新中...)

优化器&#xff08;Optimizers&#xff09; 引言1. 随机梯度下降/Stochastic Gradient Descent (SGD)2. 学习率&#xff08;Learning Rate&#xff09;3. 学习率衰减&#xff08;Learning Rate Decay&#xff09;4. 带动量的随机梯度下降法&#xff08;Stochastic Gradient Des…

利用c语言详细介绍下栈的实现

数据结构中&#xff0c;栈是一种线性结构&#xff0c;数据元素遵循后进先出的原则。栈的一端为栈顶&#xff0c;一端为栈底或栈尾&#xff0c;数据只在栈顶端进行操作。新插入数据称为入栈或者压栈&#xff0c;删除数据叫做出栈或者退栈。 一、图文介绍 我们通过建立一个stack…

Jackson、Gson、FastJSON三款JSON利器比拼

在Java领域&#xff0c;有多种JSON工具包&#xff0c;比如Jackson、Gson、FastJSON&#xff0c;每家都各有所长&#xff0c;下面我们从性能、特性、生态、易用 性等几个方面来展开下&#xff1a; 一、Jackson 性能 Jackson是一款高性能的JSON处理库。它在序列化和反序列化操作…

使用 OpenCV 进行视频中的行人检测

在计算机视觉领域&#xff0c;行人检测是一个重要的研究方向&#xff0c;它在视频监控、自动驾驶、人机交互等领域都有着广泛的应用。本文将介绍如何使用 OpenCV 库来实现视频中的行人检测。 环境准备 首先&#xff0c;我们需要安装 OpenCV 库。可以通过以下命令来安装&#…

pytest日志总结

pytest日志分为两类&#xff1a; 一、终端&#xff08;控制台&#xff09;打印的日志 1、指定-s&#xff0c;脚本中print打印出的信息会显示在终端&#xff1b; 2、pytest打印的summary信息&#xff0c;这部分是pytest 的默认输出&#xff08;例如测试结果PASSED, FAILED, S…

数据治理:在企业数据管理中的关键角色与实现路径——《DAMA 数据管理知识体系指南》读书笔记- 第 3 章

文章目录 1. 数据治理的核心内涵与战略价值2. 数据治理的驱动因素&#xff1a;不仅仅是合规3. 数据治理的组织模型&#xff1a;选择适合企业结构的运营模式4. 实施数据治理的关键步骤&#xff1a;战略、制度和文化5. 数据治理工具的选择&#xff1a;支持业务与流程的高效管理6.…