【c++篇】:解析c++类--优化编程的关键所在(一)

news2024/10/21 8:13:01

文章目录

  • 前言
  • 一.面向过程和面向对象
  • 二.c++中的类
    • 1.类的引入
    • 2.类的定义
    • 3.类的封装和访问限定符
    • 4.类的作用域
    • 5.类的实例化
    • 6.类对象模型
  • 三.`this`指针
    • 1.`this`指针的引出
    • 2.`this`指针的特性
    • 3.C语言和c++实现栈Stack的对比

前言

在程序设计的广袤宇宙中,C++以其强大的功能和灵活性,成为众多开发者手中的利器。C++不仅继承了C语言的高效和直接操作硬件的能力,还引入了面向对象编程的概念,让代码的组织和管理变得更加清晰和高效。而在C++的面向对象体系中,类(class)无疑是最核心的概念之一。而本篇文章将初步学习类(class)。

一.面向过程和面向对象

C语言是面向过程的,关注的是解决过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

c++是面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间交互完成。

比如一个外卖系统,C语言关注的是点餐,下单,付款,送单等一系列的过程,而c++则关注的是顾客,商家,骑手等对象之间的交互。

但c++中的对象又是什么呢?

带着这个问题我们先来了解一下什么是类Class

二.c++中的类

1.类的引入

在C语言中我们知道结构体struct中可以定义变量,而在c++中,struct升级成了类,结构体中不仅可以定义变量,也可以定义函数,同时可以直接用类名不带struct。以之前用C语言写的栈为例,用c++方式实现:

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
typedef int STData;
struct Stack {
    //成员函数
	void StackInit(int capacity=4) {
		_arr = (STData*)malloc(sizeof(STData) * capacity);
		if (_arr == nullptr) {
			perror("malloc fail");
			return;
		}
		_top = 0;
		_capacity = capacity;
	}
	void StackPush(STData x) {
		if (_top == _capacity) {
			STData* tmp = (STData*)realloc(_arr,sizeof(STData) * _capacity * 2);
			if (tmp == nullptr) {
				perror("malloc fail");
				return;
			}
			_arr = tmp;
			_capacity *= 2;
		}
		_arr[_top++] = x;
	}
	void StackPop() {
		if (_top == 0) {
			return;
		}
		_top--;
	}
	STData StackTop() {
		if (_top == 0) {
			return NULL;
		}
		return _arr[_top-1];
	}
	void StackDestroy() {
		free(_arr);
		_arr = nullptr;
		_top = 0;
		_capacity = 0;
	}
    
    //成员变量
	STData* _arr;
	int _top;
	int _capacity;
};
int main() {
	Stack st;
	st.StackInit(10);
	st.StackPush(1);
	st.StackPush(2);
	st.StackPush(3);
	st.StackPush(4);
	st.StackPush(5);
	st.StackPush(6);
	while (st._top != 0) {
		cout << st.StackTop() << " ";
		st.StackPop();
	}
	st.StackDestroy();
	return 0;

}

上面用结构体的定义,在c++中更喜欢用class来替代。

2.类的定义

类通过关键字class来定义,后面跟着类名classname和一对花括号{},花括号内的为类的主体也就是类的成员声明。注意花括号后的;不要省略。

class classname{
    //成员函数
    void fun(){
        ....
    }
    ....
    //成员变量
    int _a;
    char _b;
    ....
};

类体中的内容为类的成员:

  • 类中的变量称为类的属性或者成员变量,这些是类的数据部分,用于存储对象的状态信息。
  • 类中的函数称为类的方法或者成员函数,这些是类的行为部分,用于描述对象可执行的操作。成员函数可以访问和修改成员变量的值。

类有两种定义方式:

  • 声明和定义全部放在类体中:

    class Data{
        
        void dataprint(){
            cout<<_year<<_month<<_day<<endl;
        }
        
        int _year;
        int _month;
        int _day;
    };
    
  • 声明(.h)和定义(.cpp)放在不同的文件中:

    在.cpp文件定义时,成员函数名前要加类名::

    //.h文件
    class Data{
        //声明
        void Dataprint();
        
        int _year;
        int _month;
        int _day;
    };
    //.cpp文件
    //定义
    void Data::Dataprint(){
        cout<<_year<<_month<<_day<<endl;
    }
    

成员变量命名规则建议:

如果成员变量名和成员函数参数名相同时就会容易混淆不易区分,比如:

class Data{
    void Dataprint(int year){
        year=year;
        cout<<year<<month<<day<<endl;
    }
    int year;
    int month;
    int day;
};

为了解决这种情况,通常习惯加一些字符,比如一些公司要求成员变量名前加_,有的会将_加在成员变量名后,有的也会用其他字符区分。主要还是看公司要求,我们在日常练习时,可以根据自己喜好来设定。

class Data{
    void Dataprint(int year){
        _year=year;
        cout<<_year<<_month<<_day<<endl;
    }
    int _year;
    int _month;
    int _day;
};

3.类的封装和访问限定符

前面我们了解到c++是面向对象的,而面向对象具有三大特性:

封装,继承,多态

在类和对象阶段,我们首先来学习类的封装特性。

封装是面向对象编程的一个基本原则,在c++语言实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部的实现细节,控制那些方法可以在类外部直接使用,提高代码的安全性和可维护性。

注意:通过访问权限限定的某些方法虽然不能在类外部直接使用,但是在类内部还是可以访问的。

而如何实现封装呢?

这就要借助c++的访问限定符,public(公有),protected(保护),private(私有)。比如:

class Data{
//公有
public:
    void Dataprint();
//私有
private: 
    int _year;
    int _month;
    int _day;
};
  • public修饰的成员在类外可以直接被访问
  • protectedprivate修饰的成员在类外不能直接被访问,但是protected成员在派生类(子类)中可以访问private成员只能在类内部访问
  • 访问权限作用域从该访问符出现的位置开始直到下一个访问限定符出现时为止。如果后面没有访问限定符,作用域就到}也就是类结束为止。
  • class的默认访问权限为private,struct的为public

4.类的作用域

在c++入门的时候我们学过一个新的域,叫命名空间域,而现在我们了解到类之后要再认识一个新的域,也就是类域

类定义了一个新的作用域为类域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用::域作用限定符来指明该成员属于哪个类域。

class Data{
public:
    void Dataprint();
private: 
    int _year;
    int _month;
    int _day;
};
//这里需要指定Dataprint是属于Data这个类域中
void Data::Dataprint(){
    cout<<_year<<_month<<_day<<endl;
}

5.类的实例化

类定义了对象的类型,但类本身不是对象,因此我们要创建对象。

而用类类型创建对象的过程,就叫类的实例化。

  • 类是对对象进行描述的,是一个模型一样的东西,限定了类都有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。而类实例化对象,就是为该对象开辟空间来进行存储。

    //以实例化一个栈为例
    class Stack{
    public:
        void Init();
        void Destroy();
        ...
    private:
        int*a;
        int top;
        int capacity;
    };
    int main(){
        //类实例化对象/对象定义
        Stack st;
    }
    
  • 一个类可以实例化出多个对象,实例化出的对象,占用实际的物理空间,存储类成员变量capaciyt是没用空间的,只有实例化的对象st1才有具体的容量。

    //以实例化一个栈为例
    class Stack{
    public:
        void Init();
        void Destroy();
        ...
        //类的成员变量是声明不是定义
        //声明和定义的区别是,声明不开空间而定义开空间
        int*a;
        int top;
        int capacity;
    };
    int main(){
        //类实例化多个对象
        Stack st1;
        Stack st2;
        //错误,声明没有开辟空间不能存储数据
        Stack::capacity=4;
        //正确
        st1.capacity=4;
    }
    

    在这里插入图片描述

  • 做个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就是设计图,只是设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

6.类对象模型

我们知道类中既有成员变量又可以有成员函数,那么一个类的对象中包含了什么?而一个类的大小又该怎样计算?

我们先假设类对象的存储方式为包含类的各个成员:

在这里插入图片描述

如果是上面这种情况,每个对象的成员变量不同,但是会调用相同的函数,按照这种方式存储,当一个类创建多个对象时,每个对象都有一份相同的函数,就会大大浪费空间,那么如何解决呢?

每个对象只存储成员变量,没有存储成员函数,成员函数存放在公共的代码段

在这里插入图片描述

上面这种存储方式大大节省了内存空间同时又提高了代码的可重用性。

明白了上面的之后我们在来看以下几种情况:

//类中既有成员变量,又有成员函数
class A1{
public:
    void f1();
private:
    int _a;
    
};
//类中只有成员函数
class A2{
public:
    void f2();
};
//类中什么都没有,也就是空类
class A3{
  
};

上面这三种情况类的大小:

sizeof(A1)=4;sizeof(A2)=1;sizeof(A3)=1;

结论:一个类的大小,实际就是该类中成员变量之和,注意内存对齐。而空类或者是没有成员变量的大小为1字节,是为了占位,表示对象存在,但不存储有效数据。

三.this指针

1.this指针的引出

我们先来看一下下面这个日期类Data

class Data {
public:
	void Init(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	void print() {
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() {
	Data d1;
	Data d2;
	d1.Init(2024, 10, 18);
	d2.Init(2024, 10, 19);
	d1.print();
	d2.print();

	return 0;
}

在上面这段代码中,我们定义了两个日期类对象d1d2Data类中有两个成员函数Init,printd1d2都调用了这两个函数,那么又是如何对这两个不同的对象调用相同的函数进行区分呢?

c++中为了解决这种问题引入了this指针,对于非静态成员函数来说,他们实际上都隐含了一个指向当前对象的指针,也就是this指针,这个this指针在函数调用时自动传递,指向调用该成员函数的对象。这样,尽管成员函数的代码在公共段,他们依然能够通过this指针来访问和操作特定对象的成员变量。

2.this指针的特性

  • this指针的类型为:类类型*const,也就是成员函数中,不能给this指针赋值。

  • this指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this指针,所以对象中不存储this指针。

  • this指针允许在成员函数内部使用,但不能在形参和实参显示传递,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递。

  • this 指针是形参,所以this指针和普通参数一样存在函数调用的栈帧里,调用结束时,栈帧销毁,this指针也会销毁。

  • this指针可以为空。但是不能在调用的成员函数中对其解引用。比如下面两段代码:

    运行正常:

    class A1 {
    public:
    	void print() {
    		cout << "print()" << endl;
    	}
    private:
    	int _a;
    };
    
    int main() {
    	A1* p = nullptr;
    	p->print();
    	return 0;
    }
    

    在这里插入图片描述

    运行崩溃:

    class A1 {
    public:
    	void print() {
    		cout << _a << endl;
    	}
    private:
    	int _a;
    };
    
    int main() {
    	A1* p = nullptr;
    	p->print();
    	return 0;
    }
    

    在这里插入图片描述

为什么这两段代码的结果不同,虽然对象指针p都为空,p调用成员函数时作为实参传递该this指针,但是第一种情况,在成员函数中,this指针为空但没有发生解引用所以正常运行,而第二种情况this指针为空并且发生了解引用,所以运行崩溃。

3.C语言和c++实现栈Stack的对比

  • C语言实现:

    //stack.h头文件:
    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    typedef int STData;
    typedef struct Stack1 {
    	STData* array;
    	int top;
    	int capacity;
    }Stack1;
    void _InitStack(Stack1*st);
    bool _IsEmpty(Stack1*st);
    void _PushStack(Stack1*st,int x);
    void _PopStack(Stack1* st);
    STData _TopStack(Stack1* st);
    void _DestroyStack(Stack1* st);
    void _checkcapacity(Stack1* st);
    //stack.cpp定义实现文件:
    void _InitStack(Stack1* st) {
        assert(st);
    	st->array = (STData*)malloc(sizeof(STData) * 4);
    	if (st->array == nullptr) {
    		perror("malloc fail");
    		return;
    	}
    	st->top = 0;
    	st->capacity = 4;
    }
    void _checkcapacity(Stack1* st) {
        assert(st);
    	if (st->top == st->capacity) {
    		STData* tmp = (STData*)realloc(st->array, sizeof(STData) * (st->capacity) * 2);
    		if (tmp == nullptr) {
    			perror("malloc fail");
    			return;
    		}
    		st->array = tmp;
    		st->capacity *= 2;
    	}
    }
    bool _IsEmpty(Stack1* st) {
        assert(st);
    	return st->top==0;
    }
    void _PushStack(Stack1* st, int x) {
        assert(st);
    	_checkcapacity(st);
    	st->array[st->top++] = x;
    }
    void _PopStack(Stack1* st) {
        assert(st);
    	if (_IsEmpty(st)) {
    		return;
    	}
    	st->top--;
    }
    STData _TopStack(Stack1* st) {
        assert(st);
    	if (_IsEmpty(st)) {
    		return 0;
    	}
    	return st->array[st->top - 1];
    }
    void _DestroyStack(Stack1* st) {
    	free(st->array);
    	st->array = NULL;
    	st->top = 0;
    	st->capacity = 0;
    }
    
    //test.cpp测试文件:
    int main() {
    	Stack1 st1;
    	_InitStack(&st1);
    	_PushStack(&st1, 1);
    	_PushStack(&st1, 2);
    	_PushStack(&st1, 3);
    	_PushStack(&st1, 4);
    	_PushStack(&st1, 5);
    	_PushStack(&st1, 6);
    	while (!_IsEmpty(&st1)) {
    		printf("%d ", _TopStack(&st1));
    		_PopStack(&st1);
    	}
    	_DestroyStack(&st1);
    	return 0;
    }
    

    在这里插入图片描述

    在用C语言实现时,Stack相关操作函数有以下共性:

    • 每个函数的第一个参数都是Stack1*
    • 函数中必须对第一个参数检查,判断是否为空
    • 函数中都是通过Stack1*参数操作栈的
    • 调用函数时必须传递Stack1结构体变量的地址

结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据 的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。

  • c++实现:

    //stack.h头文件:
    #include<stdio.h>
    #include<stdlib.h>
    #include<iostream>
    using namespace std;
    
    typedef int STData;
    
    class Stack {
    public:
    	void InitStack();
    	bool IsEmpty();
    	void PushStack(int x);
    	void PopStack();
    	STData TopStack();
    	void DestroyStack();
    private:
    	void checkcapacity();
    	STData* _a;
    	int _top;
    	int _capacity;
    };
    //stack.cpp定义实现文件:
    #include"stack.h"
    
    void Stack::InitStack() {
    	_a = (STData*)malloc(sizeof(STData) * 4);
    	if (_a == nullptr) {
    		perror("malloc fail");
    		return;
    	}
    	_top = 0;
    	_capacity = 4;
    }
    void Stack::checkcapacity() {
    	if (_top == _capacity) {
    		STData* tmp = (STData*)realloc(_a,sizeof(STData) * _capacity * 2);
    		if (tmp == nullptr) {
    			perror("malloc fail");
    			return;
    		}
    		_a = tmp;
    		_capacity *= 2;
    	}
    }
    void Stack::PushStack(int x) {
    	checkcapacity();
    	_a[_top++] = x;
    }
    bool Stack::IsEmpty() {
    	return _top == 0;
    }
    
    void Stack::PopStack() {
    	if (IsEmpty()) {
    		return;
    	}
    	_top--;
    }
    STData Stack::TopStack() {
    	return _a[_top - 1];
    }
    void Stack::DestroyStack() {
    	free(_a);
    	_a = nullptr;
    	_top = 0;
    	_capacity = 0;
    }
    //test.cpp测试文件:
    #include"stack.h"
    int main() {
    	Stack st;
    	st.InitStack();
    	st.PushStack(1);
    	st.PushStack(2);
    	st.PushStack(3);
    	st.PushStack(4);
    	st.PushStack(5);
    	while (!st.IsEmpty()) {
    		cout << st.TopStack() << " ";
    		st.PopStack();
    	}
    	st.DestroyStack();
    	return 0;
    }
    

    在这里插入图片描述

    c++中通过类可以将数据和操作数据的方法进行有机结合,通过访问权限可以控制那些方法在类外可以被调用,也就是封装。而且通过this指针,不需要传递Stack*的参数,编译器编译之后该参数会自动还原。使用起来会非常方便。

以上就是关于c++类初步的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

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

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

相关文章

[k8s理论知识]6.k8s调度器

k8s默认调度器 k8s调度器的主要职责&#xff0c;就是为一个新创建出来的pod寻找一个适合的节点Node。这包括两个步骤&#xff0c;第一&#xff0c;从所有集群的节点中&#xff0c;根据调度算法挑选出所有可以运行该pod的节点&#xff0c;第二&#xff0c;从第一步的结果中&…

Java项目-基于springboot框架的企业客户信息反馈系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

Windows环境下Qt Creator调试模式下qDebug输出中文乱码问题

尝试修改系统的区域设置的方法&#xff1a; 可以修复问题。但会出现其它问题&#xff1a; 比如某些软件打不开&#xff0c;或者一些软件界面的中文显示乱码&#xff01; 暂时没有找到其它更好的办法。

10-Docker安装Redis

10-Docker安装Redis Docker安装Redis 以 Redis 6.0.8 为例&#xff1a; docker pull redis:6.0.8直接pull会出现以下错误 [rootdocker ~]# docker pull redis:6.0.8 Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request can…

[Python学习日记-50] Python 中的序列化模块 —— pickle 和 json

[Python学习日记-50] Python 中的序列化模块 —— pickle 和 json 简介 pickle 模块 json 模块 pickle VS json 简介 什么叫序列化&#xff1f; 序列化指的是将对象转换为可以在网络上传输或者存储到文件系统中的字节流的过程。序列化使得对象可以被保存、传输和恢复&#…

3D Slicer 教程二 ---- 数据集

上一章下载3d slicer的软件,这章从加载数据集来弄清楚3dslicer怎么使用. 一. 加载数据集 如果没有数据集,也可用用样本数据. (1) "File" --> "add Data" 可以添加图片文件夹,(试了MP4不行,内镜的视频估计不支持),添加单个图片的话,会出现一些选项, …

C++贪心

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 简介 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最好或最优的算法策略。贪心算…

【设计模式系列】抽象工厂模式

一、什么是抽象工厂模式 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一个接口&#xff0c;用于创建一系列相关或相互依赖的对象&#xff0c;而无需指定它们具体的类。这种模式允许客户端使用抽象的接口来创建一组…

AUTOSAR_EXP_ARAComAPI的5章笔记(17)

☞返回总目录 相关总结&#xff1a;AutoSar AP CM通信组总结 5.7 通信组 5.7.1 目标 通信组&#xff08;Communication Group&#xff0c;CG&#xff09;是由 AUTOSAR 定义的复合服务模板。它提供了一个通信框架&#xff0c;允许在 AUTOSAR 应用程序之间以对等方式和广播模…

第6章 元素应用CSS作业

1.使用CSS对页面网页元素加以修饰&#xff0c;制作“旅游攻略”网站。 浏览效果如下&#xff1a; HTML代码如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>旅游攻略</title><link type"t…

[JAVAEE] 线程安全问题

目录 一. 什么是线程安全 二. 线程安全问题产生的原因 三. 线程安全问题的解决 3.1 解决修改操作不是原子性的问题 > 加锁 a. 什么是锁 b. 没有加锁时 c. 加锁时 d. 死锁 e. 避免死锁 3.2 解决内存可见性的问题 > volatile关键字 (易变的, 善变的) a. 不加…

【Linux】多线程安全之道:互斥、加锁技术与底层原理

目录 1.线程的互斥 1.1.进程线程间的互斥相关背景概念 1.2.互斥量mutex的基本概念 所以多线程之间为什么要有互斥&#xff1f; 为什么抢票会抢到负数&#xff0c;无法获得正确结果&#xff1f; 为什么--操作不是原子性的呢&#xff1f; 解决方式&#xff1a; 2.三种加锁…

git add操作,文件数量太多卡咋办呢,

git add介绍 Git的add命令是用于将文件或目录添加到暂存区&#xff08;也就是索引库&#xff09;&#xff0c;以便在后续的提交&#xff08;commit&#xff09;操作中一并上传到版本库的。具体来说&#xff0c;git add命令有以下几种常见用法&#xff1a; 添加单个文件&#…

4、.Net 快速开发框架:DncZeus - 开源项目研究文章

DncZeus 是一个基于 ASP.NET Core 和 Vue.js 的前后端分离的通用后台管理系统框架&#xff0c;其愿景是成为一个易于使用且功能丰富的 .NET Core 通用后台权限管理模板系统基础框架。项目名称 "DncZeus" 由 "Dnc"(.NET Core 的缩写)和 "Zeus"(古…

CLion和Qt 联合开发环境配置教程(Windows和Linux版)

需要安装的工具CLion 和Qt CLion下载链接 :https://www.jetbrains.com.cn/clion/ 这个软件属于直接默认安装就行&#xff0c;很简单&#xff0c;不多做介绍了 Qt:https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/online_installers/ window 直接点exe Linux 先c…

el-table动态新增/删除表单行及校验规则

方式一&#xff1a; <template><el-form ref"ruleFormRef" :model"ruleForm" :rules"rules" label-width"120px" class"model-ruleForm":size"formSize" status-icon label-position"top">…

堆的使用实例

小伙伴们大家好&#xff0c;今天为大家带来一道算法题&#xff1a; 分析题意我们可知&#xff1a;数组最小元素一定位于0~k位置&#xff0c;如果我们首先将0~k位置构成最小堆&#xff0c;那么堆顶一定就是数组最小值。将堆顶拿出&#xff0c;将数组k1位置放入&#xff0c;那么数…

无人机+视频推流直播EasyCVR视频汇聚/EasyDSS平台在森林防护巡检中的解决方案

随着科技的飞速发展&#xff0c;无人机技术在各个领域的应用日益广泛&#xff0c;特别是在森林防护与巡检方面&#xff0c;无人机以其独特的优势&#xff0c;为传统林业管理带来了革命性的变化。本文将探讨无人机在森林防护巡检中的解决方案&#xff0c;分析其工作原理、优势及…

MYSQL 拼接函数

目录 1、CONCAT 2、CONCAT_WS 1、CONCAT 解释&#xff1a;用于拼接两个或多个字符串成一个字符串。如果任何一个参数为 NULL&#xff0c;则 CONCAT 函数的结果也会是 NULL。 语法格式&#xff1a;SELECT concat(column_name1,column_name2,...) FROM table_name 中文注释&…

【verilog刷题】时钟切换电路

时钟切换电路 1.基本概念-相关时钟源和无关时钟源2.基本的时钟切换电路&#xff08;组合逻辑&#xff09;2.相关时钟源无毛刺时钟切换电路3.非相关时钟源无毛刺时钟切换电路 1.基本概念-相关时钟源和无关时钟源 相关时钟源&#xff1a;时钟信号源之间存在某种同步或关联的关系…