2024.9.11

news2024/11/16 12:36:39

时钟

头文件 

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPaintEvent>
#include <QTimer>
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QTime>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void paintEvent(QPaintEvent *event) override;
public slots:
    void clock_slot();
private:
    Ui::Widget *ui;

    QTimer *timer;

    int hour;
    int minute;
    int second;

    int count = 0;
};
#endif // WIDGET_H

源文件

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    this->setFixedSize(800,800);
    timer = new QTimer(this);       //定义一个计时器
    timer->start(1000);     //启动计时器
    connect(timer,&QTimer::timeout,this,&Widget::clock_slot);   //连接每秒执行的槽函数
    QString t = QTime::currentTime().toString("h:m:s ap");  //获取时间

    QStringList list1 = t.split(" ");   //分割时间后的am/pm
    QStringList list2 = list1[0].split(":");    //分割时间中的:
    hour = list2[0].toInt();        //获取小时
    minute = list2[1].toUInt();     //获取分钟
    second = list2[2].toUInt();     //获取秒钟
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter p(this);               //设置画笔格式
    QPen pen(QColor("blue"));
    pen.setWidth(5);
    QBrush b("yellow");
    p.setPen(pen);
    p.setBrush(b);

    //把画家移动到中间
    p.translate(this->width()/2,this->height()/2);
    p.drawEllipse(QPoint(0,0),200,200); //画圆

    pen.setColor(QColor("black"));
    p.setPen(pen);
    for(int i=0;i<60;i++)           //绘制分钟线
    {
        p.rotate(6);
        p.drawLine(QPoint(200,0),QPoint(195,0));
    }

    pen.setWidth(10);
    p.setPen(pen);
    for(int i=0;i<12;i++)       //绘制时钟线
    {
        p.drawLine(QPoint(200,0),QPoint(190,0));
        p.rotate(30);
        p.drawText(QPoint(0,-170),QString("%1").arg(i+1));
    }

    //时针
    pen.setWidth(10);
    pen.setColor(QColor("red"));
    p.setPen(pen);
    p.rotate(hour*30+6*second/60/12+30*minute/60+6*count/60/12);
    p.drawLine(QPoint(0,-50),QPoint(0,5));

    //分针
    QPainter p1(this);
    p1.translate(this->width()/2,this->height()/2);
    pen.setWidth(6);
    pen.setColor(QColor("green"));
    p1.setPen(pen);
    p1.rotate(6*count/60+minute*6+6*second/60);
    p1.drawLine(QPoint(0,-80),QPoint(0,8));

    //秒针
    QPainter p2(this);
    p2.translate(this->width()/2,this->height()/2);
    pen.setWidth(3);
    pen.setColor(QColor("pink"));
    p2.setPen(pen);
    p2.rotate(6*count+6*second);
    p2.drawLine(QPoint(0,-120),QPoint(0,12));
}

void Widget::clock_slot()
{
    count++;
    this->update();
}

效果图

QTTCP服务器客户端

客户端

头文件

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpSocket>       //客户端套接字类型
#include <QMessageBox>      //消息对话框类
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_connectButton_clicked();
    void connected_slot();
    void readyRead_slot();
    void on_sendButton_clicked();

    void on_pushButton_clicked();

    void disconnected_slot();
private:
    Ui::Widget *ui;

    //定义通信用的变量
    QTcpSocket *client; //定义套接字指针
    QString usrName;    //用户名
};
#endif // WIDGET_H

 源文件

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //实例化客户端对象
    client = new QTcpSocket(this);

    //当客户端和服务器建立联系后,如果客户端接收到服务器发来的消息
    //客户端自身就会自动发射一个readyRead的信号,我们可以将
    connect(client,&QTcpSocket::readyRead,this,&Widget::readyRead_slot);

    //当客户端断开了与服务器的连接后,该客户端就会自动发射一个disconnected的信号
    //我们可以将该信号连接到自定义的槽函数中处理相关逻辑
    connect(client,&QTcpSocket::disconnected,this,&Widget::disconnected_slot);

    //如果当前客户端成功连接服务器,那么该客户端就会发射一个connected的信号
    //我们可以将该信号连接到自定义的槽函数中处理相关逻辑
    connect(client,&QTcpSocket::connected,this,&Widget::connected_slot);
}

Widget::~Widget()
{
    delete ui;
}

//连接服务器按钮对应的槽函数
void Widget::on_connectButton_clicked()
{
    //获取ui界面上的数据
    QString ip = ui->iplineEdit->text();        //ip地址
    quint16 port = ui->portlineEdit->text().toInt();    //端口号
    usrName = ui->namelineEdit->text();
    //调用套接字成员函数,连接服务器
    //函数原型:connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);
    //参数1:要被的服务器ip地址
    //参数2:服务器的端口号
    //参数3:默认为可读可写
    //返回值:无
    client->connectToHost(ip,port);
}

//处理connected信号的槽函数的定义
void Widget::connected_slot()
{
    QMessageBox::information(this,"成功","连接成功");
    //将相关组件禁用
    ui->iplineEdit->setEnabled(false);
    ui->portlineEdit->setEnabled(false);
    ui->namelineEdit->setEnabled(false);

    //向服务器发送一条消息
    QString msg = usrName + "进入聊天室";
    client->write(msg.toLocal8Bit());
}

//自定义处理readyRead信号的槽函数
void Widget::readyRead_slot()
{
    //从套接字中读取数据
    QByteArray msg = client->readAll();
    ui->msglistWidget->addItem(QString::fromLocal8Bit(msg));
}

//消息发送按钮对应的槽函数
void Widget::on_sendButton_clicked()
{
    //组织要发送的消息
    QString msg = usrName + ":" + ui->msglineEdit->text();

    //将消息发送给服务器
    client->write(msg.toLocal8Bit());

    //将消息展示到自己界面上
    //准备一个QListWidgetItem类的对象
    QListWidgetItem *item = new QListWidgetItem(msg);
    item->setTextAlignment(Qt::AlignRight);      //将文本右对齐
    ui->msglistWidget->addItem(item);

    //清空消息发送框的内容
    ui->msglineEdit->clear();
}

//断开连接按钮对应的槽函数
void Widget::on_pushButton_clicked()
{
    //执行断开连接的操作
    //准备发送消息给服务器
    QString msg = usrName + ": 离开聊天室";
    client->write(msg.toLocal8Bit());

    //断开连接
    client->disconnectFromHost();
}

//自定义处理disconne信号对应的槽函数
void Widget::disconnected_slot()
{
    QMessageBox::information(this,"提示","成功断开与服务器的连接");

    //将相关组件启用
    ui->iplineEdit->setEnabled(true);
    ui->portlineEdit->setEnabled(true);
    ui->namelineEdit->setEnabled(true);
    ui->sendButton->setEnabled(true);
}

服务器

头文件

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>       //tcp服务器类
#include <QTcpSocket>       //tcp客户端类
#include <QList>            //链表类
#include <QMessageBox>      //消息对话框类
#include <QDebug>           //信息调制类
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_startbtn_clicked();

    void newConnection_slot();//自定义

    void readReady_slot();//自定义处理readyRead信号的槽函数

private:
    Ui::Widget *ui;
    QTcpServer *server;         //定义服务器指针
    QList<QTcpSocket *> socketlist;//定义存放客户端信息的容器
};
#endif // WIDGET_H

源文件

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //实例化一个服务器对象
    server = new QTcpServer(this);


}

Widget::~Widget()
{
    delete ui;
}

//启动服务器按钮对应的槽函数
void Widget::on_startbtn_clicked()
{
    if(ui->startbtn->text() == "启动服务器")
    {
        //执行启动服务器的操作
        //获取ui界面上的端口号
        quint16 port = ui->portEdit->text().toUInt();

        //

        if(!server->listen(QHostAddress::Any,port))
        {
            QMessageBox::critical(this,"错误","服务器启动失败");
            return;
        }
        //程序执行至此,表示服务器启动成功
        QMessageBox::information(this,"成功","服务器启动成功");

        //将行编辑器设置为不可用
        ui->portEdit->setEnabled(false);
        //将按钮文本内容设置为关闭服务器
        ui->startbtn->setText("关闭服务器");

        //此时如果有客户端发来连接请求,那么该服务器就会自动发射一个newConnection的洗脑
        //我们可以将该信号连接到自定义的槽函数中,处理后续操作
        connect(server,&QTcpServer::newConnection,this,&Widget::newConnection_slot);
    }
    else
    {
        //执行关闭服务器的动作
        server->close();
        //socketlist.removeAll();
        socketlist.clear();
        //将行编辑器设置成可用状态
        ui->portEdit->setEnabled(true);
        //将按钮文本设置为启动服务器
        ui->startbtn->setText("启动服务器");
    }
}

//自定义处理newConnection信号的槽函数的实现
void Widget::newConnection_slot()
{
    qDebug()<<"有新的客户端发来请求了";

    //可以通过成员函数 nextPendingConnection函数获取最新连接的客户端套接字的地址
    //函数原型:QTcpSocket *nextPendingConnection();
    //无参函数
    //返回值:最新的一个连接的套接字地址
    QTcpSocket *s  = server->nextPendingConnection();

    //将该套接字放入客户端链表中
    socketlist.push_back(s);

    //程序执行至此,一个服务器可以对应多个客户端,已经建立了连接
    //此时,如果有某个客户端发来数据,那么该客户端套接字就会自动发送一个readyRead的信号
    //我们可以将该信号连接到自定义的槽函数中处理相关逻辑
    connect(s,&QTcpSocket::readyRead,this,&Widget::readReady_slot);
}

//自定义处理readyRead信号的槽函数的实现
void Widget::readReady_slot()
{
    //1.遍历链表中的所有客户端,如果客户端的状态为未连接,则直接移除出链表
    //函数原型:SocketState state() const;
    //功能:返回当前套接字的状态
    //返回结果为0时,表示该套接字时未连接状态
    for(int i=0;i<socketlist.size();i++)
    {
        //判断当前套接字,socketList[i]是否失效
        if(socketlist[i]->state() == 0)
        {
            //将该套接字移除链表
            socketlist.removeAt(i);
        }
    }

    //2.遍历所有客户端,判断客户端中是否有数据可读,如果有数据可读,则表示是该客户端发来的消息
    for(int i=0;i<socketlist.count();i++)
    {
        //判断当前客户端中是否有数据可读
        //函数原型:qint64 bytesAvailable() const override;
        //参数无
        //返回值:返回当前客户端套接字中的待读数据,如果没有数据,则返回0
        if(socketlist[i]->bytesAvailable() != 0)
        {
            //读取当前套接字的内容
            QByteArray msg = socketlist[i]->readAll();

            //将接收的消息展示到ui界面上
            ui->msglistWidget->addItem(QString::fromLocal8Bit(msg));

            //将收到的消息,全部发送给其他客户端
            for(int j=0;j<socketlist.length();j++)
            {
                if(i!=j)    //防止自己发给自己
                {
                    socketlist[j]->write(msg);
                }
            }
        }
    }
}

指针和引用的区别

1> 指针定义时需要使用*号,引用定义时需要使用&

2> 指针取值需要使用*号运算符完成,引用使用时直接跟目标使用方式一致

3> 指针定义时,需要给指针分配内存空间8字节,引用定义时不需要分配内存空间,引用使用的是目标的空间

4> 指针初始化后,可以改变指针的指向,但是引用初始化后,不能在改变目标了

5> 指针有二级指针,但是引用没有二级引用

6> 有空指针,但是没有空引用

7> 指针进行偏移运算时是对内存地址的偏移,而引用进行偏移时,就是对目标值的偏移

8> 指针不能指向右值,但是右值引用的目标可以是右值

9> 指针定义时可以不初始化(野指针),引用定义时必须初始化

10> 指针可以有指针数组,但是引用不能定义引用数组

C/C++的区别

C 语言是一种面向过程的编程语言

C++ 是一种面向对象的编程语言,同时也支持面向过程编程。它引入了类、对象、封装、继承、多态等面向对象的特性

Qt中信号与槽机制

1> 在对象树模型中,子组件构造时,需要指定父组件而存在

2> 子组件的生命周期由父组件进行管理

3> 当父组件展示时,会将子组件一并展示,当父组件释放空间时,会将加载在该组件上的所有子组件的空间全部释放

4> 对象树模型保证了,各个组件的内存不会泄露

5> 在栈区申请的组件,程序结束后,由系统自动回收,在堆区申请的组件,结束后,由其父组件进行回收资源

6> 父组件和子组件要用于共同的祖先类,实现的理论基础为多态

7> 每一个组件中都会拥有一个父组件指针和一个子组件链表

Qt对象树模型

所谓信号,就是信号函数,定义在类的signals权限下,是一个不完整的函数,只有函数声明,没有函数定义。返回值类型为void

信号函数不能当作普通函数一样被调用,只能被发射出去。

所谓槽,就是槽函数,定义在类的slots权限,是一个完整的函数,既有函数声明,也有函数定义,返回值类型为void

槽函数可以当作普通函数一样被调用,但是普通函数不能当作槽函数接收信号

什么情况需要多线程

在程序中因为io阻塞不能执行下一个任务时,可以使用多线程来执行另外的任务,不受到主线程io阻塞的影响而不能执行任务

多线程使用需要注意什么

临时资源被访问时因为时间片轮询机制,正在访问临时资源的线程的时间片用完了,在别的线程中访问了临时资源并修改了临时资源导致该线程再次访问临时资源是数据收到了篡改。

注意死锁问题,在使用多个同步机制时,如果线程获取锁的顺序不当,可能会导致死锁,比如一个线程所需要的资源在另一个线程中,要等另一个线程释放锁才能进行资源访问

epoll,select,poll区别

selectpoll的编程相对较为简单直接,但在处理大量文件描述符时,代码会变得较为繁琐。需要手动遍历文件描述符集合来确定哪些文件描述符有事件发生,并且需要处理不同类型的事件(可读、可写、异常)。

epoll的编程相对复杂一些,需要使用特定的函数(如epoll_createepoll_ctlepoll_wait)来进行操作。但是,由于其高效的事件通知方式和对大量文件描述符的良好支持,在复杂的网络编程场景中,使用epoll可以使代码更加清晰和易于维护。

智能指针

申请了内存空间,使用后忘记释放内存空间,堆区对象没有得到析构而导致内存泄露。

通过只能指针托管堆区空间可以在程序结束时,通过智能指针的析构函数自动释放空间

分为独占智能指针,共享智能指针,弱智能指针

共享内存和消息队列区别

共享内存需要进程自己进行同步和数据一致性的控制。由于多个进程可以同时访问共享内存,可能会出现数据竞争和不一致的情况。因此,进程需要使用同步机制如互斥锁、信号量等来确保对共享内存的正确访问。

消息队列由内核进行管理,通常提供了一定程度的同步机制。发送和接收消息的操作是原子性的,保证了消息的完整性和顺序性。但是,如果多个进程同时从一个消息队列中接收消息,可能会出现竞争,需要额外的同步措施。

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

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

相关文章

国内如何优雅的用Google?无需安装任何工具!

前言 Google可以说时每个程序猿的标配&#xff0c;但由于网络问题&#xff0c;在访问Google的时候或多或少都会遇到一系列的问题&#xff0c;接下来介绍一个非常“炸裂”的Google打开方式。 LiteIcoding 在此之前&#xff0c;你需要访问这个地址 https://lite.icoding.ink 这…

【Qt】QSS的设置方式

QSS的设置方式 QWidget 中包含了 setStyleSheet ⽅法, 可以直接设置样式. 上述代码我们已经演⽰了上述设置⽅式 还可以通过 QApplication 的 setStyleSheet ⽅法设置整个程序的全局样式. 设置全局样式&#xff0c;可以将界面上所有的样式都集中到一起来组织。 全局样式优点:…

56 - II. 数组中数字出现的次数 II

comments: true difficulty: 中等 edit_url: https://github.com/doocs/leetcode/edit/main/lcof/%E9%9D%A2%E8%AF%95%E9%A2%9856%20-%20II.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0%20II/README.md 面试题 56 - II. 数…

大屏可视化常用图标效果表达

1-echarts-雷达图 2-echarts-仪表盘 3-echarts-水球图&#xff08;利用插件&#xff0c;echarts-liquidfill&#xff09; 4-element UI tree 添加连接线&#xff0c;修改样式或使用插件&#xff08;element-tree-line&#xff09; 5-echarts-漏斗图 6-echarts-饼状图嵌套 optio…

力扣刷题之2181.合并零之间的节点

题干描述 给你一个链表的头节点 head &#xff0c;该链表包含由 0 分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val 0 。 对于每两个相邻的 0 &#xff0c;请你将它们之间的所有节点合并成一个节点&#xff0c;其值是所有已合并节点的值之和。然后将所有 0 …

为什么 1T 的硬盘容量只有 931G?真相在这里!

硬盘容量疑问 以一个容量为 1T 的硬盘为例&#xff0c;在 Windows 系统下&#xff0c;容量显示只有 931G&#xff0c;不应该是 1024GB 吗&#xff1f;这到底是为什么呢&#xff1f;是商家在欺骗消费者吗&#xff1f; 按照之前内存大小的计算逻辑&#xff08;1MB 1024KB&…

AI电商,如何提高设计效率?

第一步&#xff1a;找参考 第二步&#xff1a;提取关键词 我用的文心一言 第三步&#xff1a;选择AI绘画工具&#xff08;千鹿 设计助手&#xff09; 千鹿设计助手——FLux文生图&#xff0c;你也可以选择你手上的AI绘画工具 这个新用户注册会赠送1000积分 第四步生图

[笔记] 电机工作制以及软硬特性的本质推导

原始资料来源&#xff1a;某电机厂商 1.电机非常规操作术语和许可次数 1.1 电机操作术语 点动&#xff1a;通电后立即关停&#xff0c;最终速度不到额定转速的1/4电制动&#xff1a;制动到额定转速的1/3逆转&#xff1a;也就是打反车&#xff0c;不等停车&#xff0c;立即翻…

Java、python、php三个版本 抗震救灾物资管理系统 抗洪救灾物资分配系统 救援物资申请平台(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

在 Web 中判断页面是不是刷新

在 Web 开发中&#xff0c;我们经常需要区分用户是否通过刷新操作重新加载了页面。这一操作可能是由用户手动刷新&#xff08;如按下 F5 键或点击浏览器刷新按钮&#xff09;或通过浏览器自动重新加载。判断页面是否刷新有助于开发者优化用户体验&#xff0c;例如在使用 vue 的…

超详细,手把手带你源码启动 Thingsboard-Gateway + MQTT 接入设备

超详细&#xff0c;手把手带你源码启动 Thingsboard-Gateway MQTT 接入设备 前置条件 thingsboard&#xff0c;我这里选择的是本地源码启动postgresql&#xff0c;这里采用的是个人服务器部署的公共服务EMQX&#xff0c;这里同样采用服务器部署的公共服务MQTTX 客户端Mysql【…

Fiddle的使用------一个非常好用且正规的抓包工具

Fiddle的下载安装&#xff08;看完再去下载安装&#xff09; https://www.telerik.com/download/fiddler 1.点击连接&#xff0c;在表格填上数据&#xff0c;点击下载&#xff0c;下载结束了就安装&#xff0c;一路next就可以了。 2.修改一下设置 以上跟我一样设置&#xff…

Unity 是否能和黑神话悟空一样,接入Nivida的DLSS,用NSight Graphics实际测试

NSight作为Nivida 显卡的调试工具&#xff0c;因为国内都是手游开发盛行的年代&#xff0c;远没有RenderDoc或者高通的QuatXXX 出名 选择NSight的原因很简单&#xff1a; Nividia 财大气粗&#xff0c;倒不是主因&#xff0c; 因为其CEO爱出名&#xff0c;所以手下的人只…

视觉SLAM ch5——相机与图像

一、单目模型 前言&#xff1a;本大标题下1~4部分讲述的都是单目针孔相机 SLAM的数学本质可以抽象为运动方程&#xff08;x&#xff09;和观测方程&#xff08;z&#xff09;&#xff08;书上的第二部分&#xff09; 教材第二章截图 书中P24页截图 其中的未知量为xk&#xff…

828华为云征文|几分钟,即可在华为云Flexus X服务器部署安全稳定的——水果生鲜商城配送小程序

最近由于公司需要开发一个水果生鲜同城配送的小程序&#xff0c;源码代码已经有了&#xff0c;相对于应的功能也开发的七七八八了&#xff0c;随着生鲜商城小程序的相对于应的功能开发逐渐接近尾声。 然而&#xff0c;在这个关键时刻&#xff0c;一个至关重要的决定摆在了团队面…

javase复习day22泛型、set、数据结构

泛型 package MyGenerics;import java.util.ArrayList; import java.util.Iterator;public class GenericsTest1 {public static void main(String[] args) {//没有泛型的情况ArrayList list new ArrayList();//所有数据都被认为是Object类型&#xff0c;都可以加入集合中list…

【操作系统】汇总二、进程管理

进程管理 二、进程与线程 文章目录 进程管理二、进程与线程1.程序1.1顺序执行的特征1.2并发执行的特征1.3 C语言编写的程序 2.进程Process2.1定义&#xff08;组织&#xff09;2.1.1程序段2.1.2数据段❗2.1.3进程控制块PCB1&#xff09;内容2&#xff09;作用3&#xff09;进程…

C++详解string(全面解析)

目录 string的概念&#xff1a; string的框架&#xff1a; 1、成员函数 2、迭代器&#xff08;Iterators&#xff09;​编辑 3、容量 4、元素访问 5、修改 6、非成员函数重载 string的构造和拷贝构造&#xff1a; string的析构&#xff1a; string的访问&#xff1a;…

树模式数据表设计学习

引子&#xff1a; 场景&#xff1a;某读书网站&#xff0c;支持读者评论文章&#xff0c;并且对评论支持回复功能。设计的表如下&#xff1a; 问题点&#xff1a;你想获取一个评论下所有的评论信息&#xff1f; 将所有评论一次性取出、轮巡遍历&#xff0c;获取到所有数据。 …

几款可以让销售管理事倍功半的CRM软件推荐!

本文将盘点几款CRM软件&#xff0c;为企业选型提供参考&#xff01; 想象一下这样一个场景&#xff0c;一家企业的销售团队每天忙碌地跟进客户&#xff0c;却因为信息分散、管理混乱而效率低下。CRM 软件就如同一位得力助手&#xff0c;将客户信息有序整合&#xff0c;助力企业…