怎样才算学会了C++基础,一篇文章学习了解!

点击蓝字

363ffccfcd0ffcf6b849f6624a0ea00a.png

关注我们

因公众号更改推送规则,请点“在看”并加“星标”第一时间获取精彩技术分享

来源于网络,侵删

内容:C++、STL、数据结构、TCP/IP、数据库、linux、Qt

C++的基础(推荐两本书C++ primer和C++ primer plus)

语法部分

C++的基础语法包括以下几个方面:

  1. 注释

C++支持单行注释(以“//”开头)和多行注释(以“/”开头,“/”结尾)。

  1. 标识符

标识符是指变量、函数、类、结构体等的名称。标识符必须以字母或下划线开头,后面可以是字母、数字或下划线。C++对大小写敏感。

  1. 关键字

C++有一些关键字,这些关键字具有特殊的含义,不能用作标识符。例如:int、double、if、else、for、while等。

  1. 数据类型

C++有基本数据类型和用户自定义数据类型。基本数据类型包括整型、浮点型、字符型、布尔型等。用户自定义数据类型包括结构体、枚举、类等。

  1. 变量

变量是指用来存储数据的内存位置。定义变量时需要指定变量的数据类型和名称。变量的值可以在程序运行过程中被修改。

  1. 常量

常量是指不能被修改的值。C++有字面常量和符号常量。字面常量是指直接在代码中使用的常量值,例如:10、3.14、'A'等。符号常量是指用#define或const关键字定义的常量,例如:#define PI 3.14、const int MAX_NUM = 100等。

  1. 运算符

C++支持算术运算符、关系运算符、逻辑运算符、位运算符等。例如:+、-、*、/、%、==、!=、<、>、&&、||、&、|等。

  1. 控制语句

C++有选择结构和循环结构两种控制语句。选择结构包括if语句和switch语句,循环结构包括while语句、do-while语句和for语句。这些控制语句可以根据条件执行不同的代码块,从而控制程序的执行流程。

  1. 函数

预处理/编译/汇编/链接

预处理:预定义宏 编译:编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。汇编:汇编语言代码翻译成目标机器指令的过程 连接:链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

内存分区

C++的内存分区主要包括以下几个部分:

  1. 栈区(Stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其特点是先进后出。

  2. 堆区(Heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收,其特点是先进先出。

  3. 全局区(Static):存放全局变量和静态变量,包括初始化的和未初始化的,程序结束后由操作系统回收。

  4. 常量区(Const):存放常量,如字符串常量等,程序结束后由操作系统回收。

  5. 代码区(Code):存放程序的代码,由操作系统分配和回收,不允许写入。

在程序运行过程中,栈区、堆区和全局区的大小可以动态变化,而常量区和代码区的大小是固定的。程序员在使用内存时,需要注意内存分配和释放,以避免内存泄漏和内存溢出等问题。

  1. 栈区:函数中的局部变量,比如:

void func() {
  int num = 10; // num 是一个局部变量,存储在栈区
  // ...
}
  1. 堆区:动态分配的内存,比如:

int* ptr = new int; // 动态分配一个 int 类型的内存,存储在堆区
  1. 全局区:全局变量和静态变量,比如:

int global_num = 100; // 全局变量,存储在全局区
static int static_num = 200; // 静态变量,存储在全局区
  1. 常量区:常量字符串,比如:

const char* str = "Hello, world!"; // 常量字符串,存储在常量区
  1. 代码区:程序的代码,比如:

int main() {
  // 程序的代码,存储在代码区
  return 0;
}
指针与引用

C++中,引用和指针都可以用来间接访问变量,但它们之间有以下区别:

  1. 引用必须在定义时被初始化,而指针可以先定义再赋值。

  2. 引用不允许指向空,而指针可以指向空。

  3. 引用在使用时不需要解引用操作,而指针需要使用星号(*)操作符来解引用。

下面是一个例子,演示了引用和指针的使用:

#include <iostream>
using namespace std;

int main() {
 int a = 10;
 int& ref = a; // 定义引用
 int* ptr = &a; // 定义指针

 cout << "a = " << a << endl;
 cout << "ref = " << ref << endl;
 cout << "*ptr = " << *ptr << endl;

 ref = 20; // 修改引用的值
 (*ptr) = 30; // 修改指针所指向的值

 cout << "a = " << a << endl;
 cout << "ref = " << ref << endl;
 cout << "*ptr = " << *ptr << endl;

 int b = 40;
 ptr = &b; // 指针可以重新指向另一个变量
 // ref = b; // 引用一旦指向变量后不能再指向其他变量

 cout << "b = " << b << endl;
 cout << "*ptr = " << *ptr << endl;

 return 0;
}

输出结果:

a = 10
ref = 10
*ptr = 10
a = 30
ref = 30
*ptr = 30
b = 40
*ptr = 40

在函数参数中,引用和指针都可以用来传递参数,但它们之间也有一些区别:

  1. 引用作为函数参数时,传递的是实参的别名,函数内部对引用的修改会直接影响到实参的值。而指针作为函数参数时,传递的是实参的地址,需要使用解引用操作符(*)来访问实参的值,函数内部对指针的修改不会影响到实参的地址,但可以通过指针解引用修改实参的值。

  2. 引用作为函数参数时,不需要使用取地址符(&)来传递参数,而指针作为函数参数时需要使用取地址符(&)来传递参数。

  3. 引用作为函数参数时,不能为null,而指针作为函数参数时可以为null。

下面是一个例子,演示了引用和指针作为函数参数的使用:

#include <iostream>
using namespace std;

void changeByRef(int& x) {
 x = 20;
}

void changeByPtr(int* x) {
 (*x) = 30;
}

int main() {
 int a = 10;

 changeByRef(a);
 cout << "a = " << a << endl;

 changeByPtr(&a);
 cout << "a = " << a << endl;

 int* ptr = nullptr;
 // changeByRef(ptr); // 引用不能为null
 changeByPtr(ptr); // 指针可以为null

 return 0;
}

输出结果:

a = 20
a = 30

可以看到,通过引用和指针都可以修改实参的值,但引用更加简洁,不需要使用解引用操作符(*),并且可以避免空指针的问题。但指针可以指向null,有时候也是很有用的。

C++的构造函数与析构函数(这部分看似简单实则细节很多)

C++的构造函数和析构函数是特殊的成员函数,用于在对象创建和销毁时执行特定的操作。构造函数用于初始化对象的成员变量,而析构函数用于释放对象占用的资源。

以下是一个示例代码:

#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor called" << std::endl;
    }

    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
};

int main() {
    MyClass obj; // 创建对象时调用构造函数
    return 0; // 程序结束时调用析构函数
}

注意事项:

  1. 构造函数和析构函数的名称与类名相同,且没有返回类型。

  2. 构造函数可以有参数,用于初始化成员变量,也可以没有参数。

  3. 如果没有定义构造函数,则编译器会生成默认的构造函数。

  4. 如果一个类定义了析构函数,则编译器不会生成默认的析构函数。

  5. 构造函数和析构函数的访问权限可以是public、protected或private。

  6. 构造函数和析构函数不能被继承。

  7. 在构造函数中,可以使用初始化列表来初始化成员变量,这比在构造函数体中赋值更高效。

  8. 在析构函数中,应该释放对象占用的资源,如堆内存、文件句柄等。

C++面向对象

C++的三大特征是:

  1. 封装

封装是将数据和操作数据的方法(函数)组合在一起,形成一个类。类将数据和方法封装在一起,使得数据不能被外部直接访问和修改,只能通过类的公共接口进行访问和修改。这样可以保证数据的安全性和可靠性,同时也提高了代码的可维护性和可扩展性。

下面举一个例子来说明封装的概念。假设我们要写一个银行账户管理系统,每个账户包括账户名称、账户号码、账户余额等信息。我们可以定义一个Account类来封装这些信息,具体实现如下:

class Account {
private: // 私有成员变量,外部代码无法访问和修改
    string name; // 账户名称
    string number; // 账户号码
    double balance; // 账户余额

public: // 公共成员函数,外部代码可以调用
    Account(string n, string num, double bal) { // 构造函数
        name = n;
        number = num;
        balance = bal;
    }

    string getName() { // 获取账户名称
        return name;
    }

    string getNumber() { // 获取账户号码
        return number;
    }

    double getBalance() { // 获取账户余额
        return balance;
    }

    void deposit(double amount) { // 存款操作
        balance += amount;
    }

    void withdraw(double amount) { // 取款操作
        if (balance < amount) {
            cout << "余额不足!" << endl;
        } else {
            balance -= amount;
        }
    }
};

在上面的代码中,我们将数据成员(name、number、balance)声明为私有的,外部代码无法直接访问和修改这些数据。同时,我们提供了公共成员函数(getName、getNumber、getBalance、deposit、withdraw)来访问和修改这些数据。这样,外部代码只能通过公共接口来操作数据,从而保证了数据的安全性和可靠性。

例如,我们可以通过以下代码创建一个账户对象,并进行存款和取款操作:

Account myAccount("John Smith", "1234567890", 1000.0);
myAccount.deposit(500.0);
myAccount.withdraw(2000.0);

通过封装,我们可以将数据和操作数据的方法封装在一起,提高了代码的可维护性和可扩展性,同时也保证了数据的安全性和可靠性。

  1. 继承

继承是指一个类可以从另一个类中继承属性和方法。被继承的类称为基类或父类,继承的类称为派生类或子类。子类可以重写父类的方法,也可以添加新的属性和方法。继承可以提高代码的重用性和扩展性,同时也可以实现多态性。

C++继承是面向对象编程中的一种重要概念,它允许一个类继承另一个类的属性和方法。C++支持单继承和多继承两种方式。

单继承:一个派生类只能继承一个基类的属性和方法。语法格式如下:

class 派生类名 : 继承方式 基类名 {
    // 派生类的成员和方法
};

继承方式可以是public、protected或private,分别表示公有继承、保护继承和私有继承。

下面是一个简单的单继承示例:

class Animal {
public:
    string name;
    void eat() {
        cout << name << " is eating..." << endl;
    }
};

class Cat : public Animal {
public:
    void meow() {
        cout << name << " is meowing..." << endl;
    }
};

int main() {
    Cat cat;
    cat.name = "Tom";
    cat.eat();  // 继承自父类的方法
    cat.meow(); // 子类自己的方法
    return 0;
}

在这个示例中,Animal是基类,Cat是派生类。Cat继承了Animal的属性和方法,并且添加了自己的方法meow。创建Cat的实例后,可以调用父类的方法eat和子类自己的方法meow。

多继承:一个派生类可以继承多个基类的属性和方法。语法格式如下:

class 派生类名 : 继承方式 基类名1, 继承方式 基类名2, ... {
    // 派生类的成员和方法
};

下面是一个简单的多继承示例:

class Animal {
public:
    string name;
    void eat() {
        cout << name << " is eating..." << endl;
    }
};

class Flyable {
public:
    void fly() {
        cout << "I am flying..." << endl;
    }
};

class Bird : public Animal, public Flyable {
public:
    void chirp() {
        cout << name << " is chirping..." << endl;
    }
};

int main() {
    Bird bird;
    bird.name = "Sparrow";
    bird.eat();  // 继承自Animal的方法
    bird.fly();  // 继承自Flyable的方法
    bird.chirp(); // 子类自己的方法
    return 0;
}

在这个示例中,Bird继承了Animal和Flyable两个基类的属性和方法,并且添加了自己的方法chirp。创建Bird的实例后,可以调用父类的方法eat和fly以及子类自己的方法chirp。

C++继承还涉及到虚函数、重载、多态等高级特性,这些特性可以更好地组织和管理复杂的程序。

  1. 多态

多态是指同一种类型的对象,在不同的情况下表现出不同的行为。C++实现多态的方式有两种:虚函数和模板。虚函数是指在基类中定义一个虚函数,在派生类中可以重写该函数,通过基类指针或引用调用该函数时,会根据指向的对象类型来调用相应的函数。模板是指定义一个通用的函数或类,可以接受不同类型的参数,根据参数类型的不同,会生成不同的函数或类,从而实现多态。多态可以提高代码的灵活性和可扩展性。

C++多态是面向对象编程中的一种重要概念,它允许不同的对象对同一个消息作出不同的响应。多态可以提高代码的可维护性和可扩展性,使得程序更加灵活。

C++多态实现的基础是虚函数(Virtual Function),虚函数是在基类中声明的函数,在派生类中可以被重写(Override)。通过在基类中将函数声明为虚函数,可以使得派生类中的同名函数自动成为虚函数,并且可以被动态绑定(Dynamic Binding)。

下面是一个简单的多态示例:

class Animal {
public:
    virtual void speak() {
        cout << "The animal is speaking..." << endl;
    }
};

class Cat : public Animal {
public:
    void speak() {
        cout << "The cat is meowing..." << endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        cout << "The dog is barking..." << endl;
    }
};

int main() {
    Animal* animal1 = new Cat();
    Animal* animal2 = new Dog();
    animal1->speak(); // 多态调用Cat的speak方法
    animal2->speak(); // 多态调用Dog的speak方法
    return 0;
}

在这个示例中,Animal是基类,Cat和Dog是派生类。Animal中的speak函数被声明为虚函数,Cat和Dog重写了speak函数。创建Animal的指针指向Cat和Dog的实例后,调用speak函数会根据实际指向的对象来动态绑定,从而实现了多态。

C++多态还可以通过抽象类(Abstract Class)和纯虚函数(Pure Virtual Function)实现。抽象类是不能被实例化的类,它包含至少一个纯虚函数,纯虚函数是没有实现的虚函数,派生类必须重写纯虚函数才能被实例化。抽象类和纯虚函数可以强制规定派生类必须实现的接口,从而使得程序更加健壮和可靠。

C++中的模板是一种通用的编程工具,它允许我们编写可重用的代码,以适应不同类型的数据。模板可以定义类模板和函数模板,其中类模板用于定义通用的数据类型,而函数模板用于定义通用的函数。

类模板示例:

template <typename T>
class myVector {
private:
    T* data;
    int size;
public:
    myVector(int n) {
        data = new T[n];
        size = n;
    }
    T& operator[](int index) {
        return data[index];
    }
};

在此示例中,我们定义了一个名为myVector的类模板,它使用类型T作为参数。该类有一个私有数据成员data,它是一个指向T类型的指针,还有一个size变量,用于存储向量的大小。该类还定义了一个构造函数和一个运算符[],用于访问向量中的元素。

函数模板示例:

template<typename T>
T maximum(T x, T y) {
    return (x > y) ? x : y;
}

在此示例中,我们定义了一个名为maximum的函数模板,它使用类型T作为参数。该函数接受两个参数x和y,它们必须是相同类型的。该函数比较x和y的值,并返回较大的那个。

使用模板的示例:

myVector<int> v(5);
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
v[4] = 5;
cout << maximum(3, 5) << endl;
cout << maximum(3.5, 2.8) << endl;
cout << maximum('a', 'b') << endl;

在此示例中,我们首先创建了一个myVector对象v,它存储了5个整数。然后,我们使用maximum函数模板来比较不同类型的值,例如整数、浮点数和字符。这些值都是模板参数T的实例化。

C++11的特性

C++11是C++语言的一个重要版本,引入了许多新特性,包括语言特性、标准库特性等。下面是C++11的一些新特性:

  1. 自动类型推导(auto关键字)

C++11引入了auto关键字,可以自动推导变量的类型:

auto i = 42; // i的类型为int
auto d = 3.14; // d的类型为double
  1. 基于范围的for循环(range-based for循环)

C++11引入了基于范围的for循环,可以方便地遍历容器中的元素:

std::vector<int> v = {1, 2, 3};
for (auto i : v) {
    std::cout << i << std::endl;
}
  1. nullptr关键字

C++11引入了nullptr关键字,可以代替NULL指针常量:

int* p = nullptr; // p指向空指针
  1. 右值引用(rvalue reference)

C++11引入了右值引用,可以绑定到右值表达式上:

int&& r = 1; // r绑定到右值1上

右值引用可以用于实现移动语义和完美转发。

  1. 移动语义(move semantics)

C++11引入了移动语义,可以将资源所有权从一个对象转移到另一个对象,避免不必要的复制操作:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // 转移v1的资源所有权到v2
  1. lambda表达式

C++11引入了lambda表达式,可以方便地定义匿名函数:

auto f = [](int x, int y) { return x + y; }; // 定义lambda表达式
int z = f(1, 2); // 调用lambda表达式
  1. constexpr关键字

C++11引入了constexpr关键字,可以在编译时求值:

constexpr int fac(int n) {
    return n == 0 ? 1 : n * fac(n - 1);
}
int x = fac(5); // 在编译时求出5的阶乘
  1. 智能指针(smart pointers)

C++11引入了智能指针,可以自动管理资源的生命周期,避免内存泄漏:

std::unique_ptr<int> p(new int(42)); // 定义一个独占指针
std::shared_ptr<int> q(new int(42)); // 定义一个共享指针
  1. 回调函数

#include <iostream>

void callback() {

 std::cout << "回调函数的执行!"<<std::endl;
}

void dodo(void (*callback)()) {

 std::cout << "调用回调函数的函数"<<std::endl;
 callback();
}

int main() {

 dodo(callback);
 return 0;
}
  1. thread多线程与共享内存&互斥量mutex

#include<iostream>
#include <chrono>  //时间库
#include<mutex> 
#include<list>
#include<functional>  //匿名函数库
#include<condition_variable>
using namespace std;


class SyncQueue {

public:
 // 初始化队列中的数量
 SyncQueue(int maxSize) :m_maxSize(maxSize) {}

 // 放入
 void put(const int& x) {
  // 添加循环
  while (1) {
   //条件变量只能与unique_lock一起使用
   unique_lock<mutex> locker(m_mutex);
   m_notFull.wait(locker, [this]() {
    return m_queue.size() != m_maxSize;
    });
   m_queue.push_back(x);
   cout << "生产者产出:" << x << endl;
   // 加上延迟时间
   this_thread::sleep_for(std::chrono::seconds(1));
   m_notEmpty.notify_one();
  }
 }

 //拿出
 void take() {
  // 添加循环
  while (1) {
   //条件变量只能与unique_lock一起使用
   unique_lock<mutex> locker(m_mutex);
   m_notEmpty.wait(locker, [this]() {
    return !m_queue.empty(); });
   int x = m_queue.front();
   m_queue.pop_front();
   cout << "消费者消费:" << x << endl;
   // 加上延迟时间
   this_thread::sleep_for(std::chrono::seconds(1));
   m_notFull.notify_one();
  }
 }

private:
 list<int> m_queue;  //存储队列数据
 mutex m_mutex;  //互斥锁
 condition_variable m_notEmpty;  //不为空的条件变量
 condition_variable m_notFull;   //不为满的条件变量

 int m_maxSize;  //任务队列的最大任务个数
};

int main() {

 SyncQueue Tasks(5);

 //绑定生成函数
 auto producer = bind(&SyncQueue::put, &Tasks, placeholders::_1);
 auto consume = bind(&SyncQueue::take, &Tasks);

 thread t1[3];
 thread t2[3];

 for (int i = 0; i < 3; ++i) {
  t1[i] = thread(producer, i + 100);
  t2[i] = thread(consume);
 }

 for (int i = 0; i < 3; ++i) {
  t1[i].join();
  t2[i].join();
 }

 return 0;
}

STL标准模板库

STL(Standard Template Library)是C++标准库的一个重要组成部分,它提供了一组通用的模板类和函数,用于实现各种常用的数据结构和算法,如容器、迭代器、算法和函数对象等,使得C++程序员可以更加方便地进行编程。

STL库的主要组成部分包括以下几个方面:

  1. 容器(Containers):STL提供了多种容器,如vector、list、deque、set、map等,用于存储和管理不同类型的数据。这些容器实现了各种不同的数据结构,如数组、链表、树等,提供了丰富的接口和算法,方便快捷地进行数据操作。

  2. 迭代器(Iterators):STL提供了多种迭代器,如输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器等,用于遍历容器中的元素。迭代器提供了一种统一的接口,使得算法和容器之间可以进行无缝协作。

  3. 算法(Algorithms):STL提供了大量的算法,如排序、查找、拷贝、变换等,用于对容器中的元素进行处理。这些算法实现了各种常用的操作,如查找最大值、计算总和、去重复等,可以大大提高程序的效率和可读性。

  4. 函数对象(Function Objects):STL提供了多种函数对象,如一元函数对象、二元函数对象、谓词等,用于对容器中的元素进行操作。函数对象提供了一种方便的方式,使得程序员可以自定义算法和操作,从而更好地适应不同的需求。

STL库的使用可以大大提高C++程序的效率和可读性,同时也为程序员提供了更多的便利。在实际编程中,程序员可以根据不同的需求,选择合适的容器、迭代器、算法和函数对象,从而实现高效的数据操作和算法实现。

单例模式

  1. 饿汉模式:

在程序启动时就创建单例对象,因此也被称为“饱汉模式”。线程安全性较好,但是可能会浪费资源。

class Singleton {
private:
    Singleton() {}
    static Singleton* instance;
public:
    static Singleton* getInstance() {
        return instance;
    }
};

Singleton* Singleton::instance = new Singleton();
  1. 懒汉模式:

只有当需要使用单例对象时才进行创建,因此也被称为“懒汉模式”。需要考虑线程安全问题,否则可能会导致多个线程同时创建单例对象。

class Singleton {
private:
    Singleton() {}
    static Singleton* instance;
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;

线程安全问题可以通过加锁实现,例如使用std::mutex

class Singleton {
private:
    Singleton() {}
    static Singleton* instance;
    static std::mutex mtx;
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

数据结构与排序算法(推荐大话数据结构)

C++中常用的数据结构有以下几种:
  1. 数组(Array):一组连续的内存单元,用于存储同种类型的数据。

  2. 链表(Linked List):一组不连续的内存单元,用指针连接起来,可以动态地添加或删除元素。

  3. 栈(Stack):一种后进先出(LIFO)的数据结构,只能在栈顶进行插入和删除操作。

  4. 队列(Queue):一种先进先出(FIFO)的数据结构,只能在队尾进行插入操作,在队头进行删除操作。

  5. 树(Tree):一种非线性数据结构,由节点和边组成,每个节点可以有多个子节点。

  6. 图(Graph):一种非线性数据结构,由节点和边组成,每个节点可以有多个相邻节点。

  7. 堆(Heap):一种特殊的树形数据结构,常用于实现优先队列。

  8. 哈希表(Hash Table):一种通过散列函数将键映射到值的数据结构,可以实现高效的查找和插入操作。

直接插入排序的原理?

稳定,平均/最差时间复杂度 O(n²),元素基本有序时最好时间复杂度 O(n),空间复杂度 O(1)。

每一趟将一个待排序记录按其关键字的大小插入到已排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止。

public void insertionSort(int[] nums) {
    for (int i = 1; i &lt; nums.length; i++) {
        int insertNum = nums[i];
        int insertIndex;
        for (insertIndex = i - 1; insertIndex &gt;= 0 &amp;&amp; nums[insertIndex] &gt; insertNum; insertIndex--) {
            nums[insertIndex + 1] = nums[insertIndex];
        }
        nums[insertIndex + 1] = insertNum;
    }
}COPY

直接插入没有利用到要插入的序列已有序的特点,插入第 i 个元素时可以通过二分查找找到插入位置 insertIndex,再把 i~insertIndex 之间的所有元素后移一位,把第 i 个元素放在插入位置上。

public void binaryInsertionSort(int[] nums) {
    for (int i = 1; i &lt; nums.length; i++) {
        int insertNum = nums[i];
        int insertIndex = -1;
        int start = 0;
        int end = i - 1;
        while (start &lt;= end) {
            int mid = start + (end - start) / 2;
            if (insertNum &gt; nums[mid])
                start = mid + 1;
            else if (insertNum &lt; nums[mid])
                end = mid - 1;
            else {
                insertIndex = mid + 1;
                break;
            }
        }
        if (insertIndex == -1)
            insertIndex = start;
        if (i - insertIndex &gt;= 0)
            System.arraycopy(nums, insertIndex, nums, insertIndex + 1, i - insertIndex);
        nums[insertIndex] = insertNum;
    }
}
希尔排序的原理?

又称缩小增量排序,是对直接插入排序的改进,不稳定,平均时间复杂度 O(n^1.3^),最差时间复杂度 O(n²),最好时间复杂度 O(n),空间复杂度 O(1)。

把记录按下标的一定增量分组,对每组进行直接插入排序,每次排序后减小增量,当增量减至 1 时排序完毕。

public void shellSort(int[] nums) {
    for (int d = nums.length / 2; d &gt; 0 ; d /= 2) {
        for (int i = d; i &lt; nums.length; i++) {
            int insertNum = nums[i];
            int insertIndex;
            for (insertIndex = i - d; insertIndex &gt;= 0 &amp;&amp; nums[insertIndex] &gt; insertNum; insertIndex -= d) {
                nums[insertIndex + d] = nums[insertIndex];
            }
            nums[insertIndex + d] = insertNum;
        }
    }
}
直接选择排序的原理?

不稳定,时间复杂度 O(n²),空间复杂度 O(1)。

每次在未排序序列中找到最小元素,和未排序序列的第一个元素交换位置,再在剩余未排序序列中重复该操作直到所有元素排序完毕。

public void selectSort(int[] nums) {
    int minIndex;
    for (int index = 0; index &lt; nums.length - 1; index++){
        minIndex = index;
        for (int i = index + 1;i &lt; nums.length; i++){
            if(nums[i] &lt; nums[minIndex]) 
                minIndex = i;
        }
        if (index != minIndex){
            swap(nums, index, minIndex);
        }
    }
}
堆排序的原理?

是对直接选择排序的改进,不稳定,时间复杂度 O(nlogn),空间复杂度 O(1)。

将待排序记录看作完全二叉树,可以建立大根堆或小根堆,大根堆中每个节点的值都不小于它的子节点值,小根堆中每个节点的值都不大于它的子节点值。

以大根堆为例,在建堆时首先将最后一个节点作为当前节点,如果当前节点存在父节点且值大于父节点,就将当前节点和父节点交换。在移除时首先暂存根节点的值,然后用最后一个节点代替根节点并作为当前节点,如果当前节点存在子节点且值小于子节点,就将其与值较大的子节点进行交换,调整完堆后返回暂存的值。

public void add(int[] nums, int i, int num){
    nums[i] = num;
    int curIndex = i;
    while (curIndex &gt; 0) {
        int parentIndex = (curIndex - 1) / 2;
        if (nums[parentIndex] &lt; nums[curIndex]) 
            swap(nums, parentIndex, curIndex);
        else break;
        curIndex = parentIndex;
    }
}

public int remove(int[] nums, int size){
    int result = nums[0];
    nums[0] = nums[size - 1];
    int curIndex = 0;
    while (true) {
        int leftIndex = curIndex * 2 + 1;
        int rightIndex = curIndex * 2 + 2;
        if (leftIndex &gt;= size) break;
        int maxIndex = leftIndex;
        if (rightIndex &lt; size &amp;&amp; nums[maxIndex] &lt; nums[rightIndex])
            maxIndex = rightIndex;
        if (nums[curIndex] &lt; nums[maxIndex])
            swap(nums, curIndex, maxIndex);
        else break;
        curIndex = maxIndex;
    }
    return result;
}
冒泡排序的原理?

稳定,平均/最坏时间复杂度 O(n²),元素基本有序时最好时间复杂度 O(n),空间复杂度 O(1)。

比较相邻的元素,如果第一个比第二个大就进行交换,对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,每一轮排序后末尾元素都是有序的,针对 n 个元素重复以上步骤 n -1 次排序完毕。

public void bubbleSort(int[] nums) {
    for (int i = 0; i &lt; nums.length - 1; i++) {
        for (int index = 0; index &lt; nums.length - 1 - i; index++) {
            if (nums[index] &gt; nums[index + 1]) 
                swap(nums, index, index + 1)
        }
    }
}

当序列已经有序时仍会进行不必要的比较,可以设置一个标志记录是否有元素交换,如果没有直接结束比较。

public void betterBubbleSort(int[] nums) {
    boolean swap;
    for (int i = 0; i &lt; nums.length - 1; i++) {
        swap = true;
        for (int index = 0; index &lt; nums.length - 1 - i; index++) {
            if (nums[index] &gt; nums[index + 1]) {
                swap(nums, index ,index + 1);
                swap = false;
            }
        }
        if (swap) break;
    }
}
快速排序的原理?

是对冒泡排序的一种改进,不稳定,平均/最好时间复杂度 O(nlogn),元素基本有序时最坏时间复杂度 O(n²),空间复杂度 O(logn)。

首先选择一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于等于基准元素,一部分全部大于等于基准元素,再按此方法递归对这两部分数据进行快速排序。

快速排序的一次划分从两头交替搜索,直到 low 和 high 指针重合,一趟时间复杂度 O(n),整个算法的时间复杂度与划分趟数有关。

最好情况是每次划分选择的中间数恰好将当前序列等分,经过 log(n) 趟划分便可得到长度为 1 的子表,这样时间复杂度 O(nlogn)。

最坏情况是每次所选中间数是当前序列中的最大或最小元素,这使每次划分所得子表其中一个为空表 ,这样长度为 n 的数据表需要 n 趟划分,整个排序时间复杂度 O(n²)。

public void quickSort(int[] nums, int start, int end) {
    if (start &lt; end) {
        int pivotIndex = getPivotIndex(nums, start, end);
        quickSort(nums, start, pivotIndex - 1);
        quickSort(nums, pivotIndex + 1, end);
    }
}

public int getPivotIndex(int[] nums, int start, int end) {
    int pivot = nums[start];
    int low = start;
    int high = end;
    while (low &lt; high) {
        while (low &lt;= high &amp;&amp; nums[low] &lt;= pivot) 
            low++;
        while (low &lt;= high &amp;&amp; nums[high] &gt; pivot) 
            high--;
        if (low &lt; high) 
            swap(nums, low, high);
    }
    swap(nums, start, high);
    return high;
}
归并排序的原理?

归并排序基于归并操作,是一种稳定的排序算法,任何情况时间复杂度都为 O(nlogn),空间复杂度为 O(n)。

基本原理:应用分治法将待排序序列分成两部分,然后对两部分分别递归排序,最后进行合并,使用一个辅助空间并设定两个指针分别指向两个有序序列的起始元素,将指针对应的较小元素添加到辅助空间,重复该步骤到某一序列到达末尾,然后将另一序列剩余元素合并到辅助空间末尾。

适用场景:数据量大且对稳定性有要求的情况。

int[] help;

public void mergeSort(int[] arr) {
    int[] help = new int[arr.length];
    sort(arr, 0, arr.length - 1);
}

public void sort(int[] arr, int start, int end) {
    if (start == end) return;
    int mid = start + (end - start) / 2;
    sort(arr, start, mid);
    sort(arr, mid + 1, end);
    merge(arr, start, mid, end);
}

public void merge(int[] arr, int start, int mid, int end) {
    if (end + 1 - start &gt;= 0) System.arraycopy(arr, start, help, start, end + 1 - start);
    int p = start;
    int q = mid + 1;
    int index = start;
    while (p &lt;= mid &amp;&amp; q &lt;= end) {
        if (help[p] &lt; help[q]) 
            arr[index++] = help[p++];
        else 
            arr[index++] = help[q++];
    }
    while (p &lt;= mid) arr[index++] = help[p++];
    while (q &lt;= end) arr[index++] = help[q++];
}
排序算法怎么选择?

数据量规模较小,考虑直接插入或直接选择。当元素分布有序时直接插入将大大减少比较和移动记录的次数,如果不要求稳定性,可以使用直接选择,效率略高于直接插入。

数据量规模中等,选择希尔排序。

数据量规模较大,考虑堆排序(元素分布接近正序或逆序)、快速排序(元素分布随机)和归并排序(稳定性)。

TCP/UDP(推荐B站计算机网络微课堂入门)

TCP的Socket套接字是一种可靠的网络通信协议,它可以确保数据的完整性、有序性和可靠性。在C++中实现TCP的Socket套接字的客户端与服务端通信,主要分为以下几个步骤:

  1. 创建Socket:使用socket()函数创建一个Socket对象,指定协议类型和通信方式。

  2. 绑定地址:服务端需要使用bind()函数将Socket对象与一个本地地址绑定,客户端则不需要。

  3. 监听连接:服务端需要使用listen()函数开始监听客户端的连接请求,客户端不需要。

  4. 建立连接:客户端使用connect()函数向服务端发起连接请求,服务端使用accept()函数接受客户端的连接请求。

  5. 通信:客户端和服务端通过send()和recv()函数进行数据的发送和接收。

  6. 关闭连接:客户端和服务端使用close()函数关闭连接。

下面是一个简单的TCP Socket通信的例子,其中包含了客户端和服务端的代码:

服务端代码:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

using namespace std;

int main() {
    // 创建Socket
    int serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == -1) {
        cerr << "Failed to create socket" << endl;
        return -1;
    }

    // 绑定地址
    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(8080);
    if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        cerr << "Failed to bind address" << endl;
        close(serverSocket);
        return -1;
    }

    // 监听连接
    if (listen(serverSocket, 10) == -1) {
        cerr << "Failed to listen" << endl;
        close(serverSocket);
        return -1;
    }

    // 等待连接请求
    sockaddr_in clientAddr;
    socklen_t clientAddrLen = sizeof(clientAddr);
    int clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrLen);
    if (clientSocket == -1) {
        cerr << "Failed to accept connection" << endl;
        close(serverSocket);
        return -1;
    }

    // 接收客户端发送的消息
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    if (recv(clientSocket, buffer, sizeof(buffer), 0) == -1) {
        cerr << "Failed to receive data" << endl;
        close(clientSocket);
        close(serverSocket);
        return -1;
    }
    cout << "Received message from client: " << buffer << endl;

    // 发送消息给客户端
    const char* message = "Hello, client!";
    if (send(clientSocket, message, strlen(message), 0) == -1) {
        cerr << "Failed to send data" << endl;
        close(clientSocket);
        close(serverSocket);
        return -1;
    }

    // 关闭连接
    close(clientSocket);
    close(serverSocket);

    return 0;
}

客户端代码:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

using namespace std;

int main() {
    // 创建Socket
    int clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == -1) {
        cerr << "Failed to create socket" << endl;
        return -1;
    }

    // 连接服务器
    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serverAddr.sin_port = htons(8080);
    if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        cerr << "Failed to connect to server" << endl;
        close(clientSocket);
        return -1;
    }

    // 发送消息给服务器
    const char* message = "Hello, server!";
    if (send(clientSocket, message, strlen(message), 0) == -1) {
        cerr << "Failed to send data" << endl;
        close(clientSocket);
        return -1;
    }

    // 接收服务器发送的消息
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    if (recv(clientSocket, buffer, sizeof(buffer), 0) == -1) {
        cerr << "Failed to receive data" << endl;
        close(clientSocket);
        return -1;
    }
    cout << "Received message from server: " << buffer << endl;

    // 关闭连接
    close(clientSocket);

    return 0;
}

在上面的例子中,服务端监听本地地址8080端口的连接请求,并在接受到客户端的连接请求后,接收客户端发送的消息,并发送一个回复消息。客户端连接到本地地址为127.0.0.1的8080端口,并发送一条消息给服务端,然后接收服务端发送的回复消息。最后,客户端和服务端都关闭连接。

UDP的Socket套接字是一种无连接的网络通信协议,它不保证数据的可靠性和有序性,但是具有较低的延迟和较高的传输速率。在C++中实现UDP的Socket套接字的客户端与服务端通信,主要分为以下几个步骤:

  1. 创建Socket:使用socket()函数创建一个Socket对象,指定协议类型和通信方式。

  2. 绑定地址:服务端和客户端都可以使用bind()函数将Socket对象与一个本地地址绑定。

  3. 通信:客户端和服务端通过sendto()和recvfrom()函数进行数据的发送和接收。

  4. 关闭Socket:客户端和服务端使用close()函数关闭Socket。

下面是一个简单的UDP Socket通信的例子,其中包含了客户端和服务端的代码:

服务端代码:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

using namespace std;

int main() {
    // 创建Socket
    int serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (serverSocket == -1) {
        cerr << "Failed to create socket" << endl;
        return -1;
    }

    // 绑定地址
    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(8080);
    if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        cerr << "Failed to bind address" << endl;
        close(serverSocket);
        return -1;
    }

    // 接收客户端发送的消息
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    sockaddr_in clientAddr;
    socklen_t clientAddrLen = sizeof(clientAddr);
    if (recvfrom(serverSocket, buffer, sizeof(buffer), 0, (sockaddr*)&clientAddr, &clientAddrLen) == -1) {
        cerr << "Failed to receive data" << endl;
        close(serverSocket);
        return -1;
    }
    cout << "Received message from client: " << buffer << endl;

    // 发送消息给客户端
    const char* message = "Hello, client!";
    if (sendto(serverSocket, message, strlen(message), 0, (sockaddr*)&clientAddr, clientAddrLen) == -1) {
        cerr << "Failed to send data" << endl;
        close(serverSocket);
        return -1;
    }

    // 关闭Socket
    close(serverSocket);

    return 0;
}

客户端代码:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

using namespace std;

int main() {
    // 创建Socket
    int clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (clientSocket == -1) {
        cerr << "Failed to create socket" << endl;
        return -1;
    }

    // 发送消息给服务器
    const char* message = "Hello, server!";
    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serverAddr.sin_port = htons(8080);
    if (sendto(clientSocket, message, strlen(message), 0, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        cerr << "Failed to send data" << endl;
        close(clientSocket);
        return -1;
    }

    // 接收服务器发送的消息
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    sockaddr_in serverResponseAddr;
    socklen_t serverResponseAddrLen = sizeof(serverResponseAddr);
    if (recvfrom(clientSocket, buffer, sizeof(buffer), 0, (sockaddr*)&serverResponseAddr, &serverResponseAddrLen) == -1) {
        cerr << "Failed to receive data" << endl;
        close(clientSocket);
        return -1;
    }
    cout << "Received message from server: " << buffer << endl;

    // 关闭Socket
    close(clientSocket);

    return 0;
}

在上面的例子中,服务端监听本地地址8080端口的UDP连接请求,并在接受到客户端的连接请求后,接收客户端发送的消息,并发送一个回复消息。客户端连接到本地地址为127.0.0.1的8080端口,并发送一条消息给服务端,然后接收服务端发送的回复消息。最后,客户端和服务端都关闭Socket。

数据库(MYSQL)

  1. 连接数据库: mysql -u username -p password -h hostname

  2. 显示数据库列表: show databases;

  3. 创建数据库: create database dbname;

  4. 选择数据库: use dbname;

  5. 显示数据表: show tables;

  6. 创建数据表: create table tablename(column1 datatype, column2 datatype, ...);

  7. 插入数据: insert into tablename(column1, column2, ...) values(value1, value2, ...);

  8. 查询数据: select * from tablename;

  9. 更新数据: update tablename set column1=value1, column2=value2 where condition;

  10. 删除数据: delete from tablename where condition;

  11. 删除数据表: drop table tablename;

  12. 删除数据库: drop database dbname;

  13. 导入数据: source filename.sql;

  14. 导出数据: mysqldump -u username -p password dbname > filename.sql

以下是一个具体的例子,假设我们有一个学生表(students)和一个成绩表(scores),它们的结构如下:

学生表(students):

idnameage
1John20
2Alice22
3Peter19
4Tom21
5Rachel20

成绩表(scores):

idstudent_idsubjectscore
11Math80
21English85
32Math90
42English95
53Math75
63English80
74Math85
84English90
95Math95
105English100

现在我们可以使用以下命令和操作来查询和处理这些数据:

  1. 连接数据库: mysql -u root -p password -h localhost

  2. 显示数据库列表: show databases;

  3. 创建数据库: create database mydb;

  4. 选择数据库: use mydb;

  5. 显示数据表: show tables;

  6. 创建数据表:

create table students(
    id int primary key,
    name varchar(50),
    age int
);

create table scores(
    id int primary key,
    student_id int,
    subject varchar(50),
    score int,
    foreign key (student_id) references students(id)
);
  1. 插入数据:

insert into students(id, name, age) values(1, 'John', 20);
insert into students(id, name, age) values(2, 'Alice', 22);
insert into students(id, name, age) values(3, 'Peter', 19);
insert into students(id, name, age) values(4, 'Tom', 21);
insert into students(id, name, age) values(5, 'Rachel', 20);

insert into scores(id, student_id, subject, score) values(1, 1, 'Math', 80);
insert into scores(id, student_id, subject, score) values(2, 1, 'English', 85);
insert into scores(id, student_id, subject, score) values(3, 2, 'Math', 90);
insert into scores(id, student_id, subject, score) values(4, 2, 'English', 95);
insert into scores(id, student_id, subject, score) values(5, 3, 'Math', 75);
insert into scores(id, student_id, subject, score) values(6, 3, 'English', 80);
insert into scores(id, student_id, subject, score) values(7, 4, 'Math', 85);
insert into scores(id, student_id, subject, score) values(8, 4, 'English', 90);
insert into scores(id, student_id, subject, score) values(9, 5, 'Math', 95);
insert into scores(id, student_id, subject, score) values(10, 5, 'English', 100);
  1. 查询数据:

-- 查询所有学生的信息
select * from students;

-- 查询所有学生的姓名和年龄
select name, age from students;

-- 查询所有成绩大于等于90分的学生的姓名和科目
select students.name, scores.subject from students join scores on students.id=scores.student_id where score>=90;

-- 查询每个学生的平均成绩
select students.name, avg(score) from students join scores on students.id=scores.student_id group by students.id;
  1. 更新数据:

-- 将John的年龄修改为21
update students set age=21 where name='John';
  1. 删除数据:

-- 删除Peter的信息
delete from students where name='Peter';

-- 删除所有成绩表中的数据
delete from scores;
  1. 删除数据表:

drop table students;
drop table scores;
  1. 删除数据库:

drop database mydb;
  1. 导入数据:

source /path/to/filename.sql;
  1. 导出数据:

mysqldump -u root -p password mydb > /path/to/filename.sql;

linux

  1. Linux是一种开源的、免费的操作系统,其内核由Linus Torvalds在1991年创建,现在已经成为了世界上最流行的操作系统之一。

  2. 常用命令:

  • ls:列出目录下的文件和子目录。

  • cd:切换当前目录。

  • pwd:显示当前所在目录的路径。

  • mkdir:创建一个新目录。

  • rm:删除文件或目录。

  • cp:复制文件或目录。

  • mv:移动或重命名文件或目录。

  • cat:查看文件内容。

  • grep:在文件中查找指定字符串。

  • chmod:修改文件或目录的权限。

Vim是一款文本编辑器,常用于Linux和Unix系统中。它是Vi编辑器的改进版,具有强大的编辑功能和高度的可定制性。以下是Vim的一些常用命令:

  • i:在当前光标位置插入文本。

  • a:在当前光标位置的下一个字符处插入文本。

  • o:在当前光标行的下一行插入新行。

  • Esc:退出插入模式,回到命令模式。

  • :w:保存文件。

  • :q:退出Vim。

  • :q!:强制退出Vim,不保存修改。

  • :wq:保存文件并退出Vim。

  • yy:复制当前行。

  • p:将复制的文本粘贴到当前光标位置。

  • dd:删除当前行。

除了这些基本命令之外,Vim还有许多其他的高级功能,如宏录制、多窗口编辑、代码折叠等等。Vim的学习曲线可能比较陡峭,但是一旦掌握了它的基本操作,就能够提高文本编辑的效率。

  1. 下面是一个简单的C++程序,使用Vim编辑器进行编辑:

  2. 打开一个终端窗口,输入以下命令启动Vim编辑器:

    vim hello.cpp

    这将创建一个名为“hello.cpp”的新文件,并在Vim中打开它。

  3. 进入插入模式,输入以下代码:

    #include <iostream>
    
    int main()
    {
        std::cout << "Hello, world!" << std::endl;
        return 0;
    }

    这个程序会输出“Hello, world!”,然后返回0。

  4. 保存文件并退出Vim。首先按下Esc键,回到命令模式,然后输入以下命令:

    :wq

    这将保存文件并退出Vim。

  5. 使用GCC编译器编译程序。在终端窗口中输入以下命令:

    g++ -o hello hello.cpp

    这将生成一个名为“hello”的可执行文件。

  6. 运行程序。在终端窗口中输入以下命令:

    ./hello

    这将运行程序,并输出“Hello, world!”。

Qt(推荐B站爱编程的大丙与大轮明王讲QT)

Qt是一种跨平台的GUI应用程序开发框架,提供了一系列的工具和类库,使开发者可以方便地开发高质量的跨平台应用程序。Qt的主要内容包括以下几个方面:

  1. Qt核心模块:提供了Qt的基本功能,包括对象模型、信号和槽机制、容器类、文件和IO操作、多线程等。

  2. Qt Widgets模块:提供了一系列基于QWidget的GUI控件,如按钮、标签、文本框等,用于构建传统的桌面应用程序。

  3. Qt Quick模块:提供了一种基于QML的声明式语言,用于构建现代的用户界面,支持动画、视觉效果等。

  4. Qt网络模块:提供了网络编程相关的类库,如TCP和UDP套接字、HTTP和FTP协议等。

  5. Qt数据库模块:提供了用于访问各种关系型数据库的类库,如MySQL、PostgreSQL、SQLite等。

  6. Qt多媒体模块:提供了音频和视频处理相关的类库,如播放音频和视频、录制音频和视频等。

  7. Qt图形和绘图模块:提供了用于2D和3D图形渲染相关的类库,如OpenGL、QPainter等。

  8. Qt Web模块:提供了用于Web开发相关的类库,如Webkit和WebEngine。

除了以上这些模块之外,Qt还提供了一些其他的工具和类库,如Qt Creator集成开发环境、Qt Quick Controls用于构建移动应用程序等。总的来说,Qt是一个功能强大、可扩展、易于使用的开发框架,适用于开发各种不同类型的跨平台应用程序。

信号与槽机制

信号和槽机制是Qt框架中的一种事件处理机制,用于在对象之间进行通信。信号是一种特殊的函数,当特定的事件发生时,会自动调用信号,而槽则是一种函数,用于响应信号。

在Qt中,一个对象可以有多个信号和槽,它们可以在不同的对象之间连接起来,实现对象之间的通信。当信号被触发时,与之连接的槽函数会被自动调用,从而实现了对象之间的协作。

使用信号和槽机制可以使代码更加灵活、可维护和可扩展。它可以帮助开发人员更好地组织代码,降低代码的耦合度,并提高程序的可读性和可维护性。

下面是一个简单的例子,演示如何在两个对象之间使用信号和槽机制进行通信。

假设有一个窗口类和一个按钮类,当按钮被点击时,窗口会显示一个消息框。代码如下:

// 窗口类
class MyWindow : public QWidget
{
    Q_OBJECT
    
public:
    MyWindow(QWidget *parent = nullptr) : QWidget(parent)
    {
        QPushButton *button = new QPushButton("Click me", this);
        connect(button, SIGNAL(clicked()), this, SLOT(showMessage()));
    }
    
public slots:
    void showMessage()
    {
        QMessageBox::information(this, "Message", "Button clicked");
    }
};

// 按钮类
class MyButton : public QPushButton
{
    Q_OBJECT
    
public:
    MyButton(const QString &text, QWidget *parent = nullptr) : QPushButton(text, parent)
    {
        // do something
    }
};

// 主函数
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyWindow window;
    window.show();
    return app.exec();
}

在上面的代码中,我们创建了一个窗口类MyWindow和一个按钮类MyButton。在窗口类的构造函数中,我们创建了一个按钮,并将其连接到窗口类的showMessage()槽函数上。当按钮被点击时,就会触发clicked()信号,进而自动调用showMessage()槽函数,从而显示一个消息框。

这就是一个简单的使用信号和槽机制的例子。通过信号和槽的连接,我们实现了按钮和窗口之间的通信,使程序更加灵活和可扩展。

以下是一个使用Qt5的版本的信号和槽机制的例子:

// 窗口类
class MyWindow : public QWidget
{
    Q_OBJECT
    
public:
    MyWindow(QWidget *parent = nullptr) : QWidget(parent)
    {
        QPushButton *button = new QPushButton("Click me", this);
        connect(button, &QPushButton::clicked, this, &MyWindow::showMessage);
    }
    
public slots:
    void showMessage()
    {
        QMessageBox::information(this, "Message", "Button clicked");
    }
};

// 按钮类
class MyButton : public QPushButton
{
    Q_OBJECT
    
public:
    MyButton(const QString &text, QWidget *parent = nullptr) : QPushButton(text, parent)
    {
        // do something
    }
};

// 主函数
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MyWindow window;
    window.show();
    return app.exec();
}

在这个例子中,我们使用了Qt5的新语法来连接信号和槽。具体来说,我们使用了connect()函数的新语法,将信号和槽通过函数指针连接起来。

在按钮类的构造函数中,我们创建了一个按钮,并将其连接到窗口类的showMessage()槽函数上。当按钮被点击时,就会触发clicked()信号,进而自动调用showMessage()槽函数,从而显示一个消息框。

这个例子和之前的例子类似,只是使用了Qt5的新语法来连接信号和槽。这种新语法更加简洁明了,使代码更加可读性和可维护性。

Qt的几个窗口
  1. QMainWindow:主窗口,包括菜单栏、工具栏、状态栏等。例如:Qt Creator的主窗口。

  2. QDialog:对话框窗口,用于显示对话框,例如:文件选择对话框、颜色选择对话框等。

  3. QWidget:基本窗口,用于显示各种控件,例如:按钮、文本框、标签等。

  4. QDockWidget:浮动窗口,可以被拖拽到主窗口的四周,例如:Qt Creator的工程浏览器、属性编辑器等。

  5. QSplashScreen:启动画面,用于显示程序启动时的欢迎画面。例如:各种桌面应用程序的启动画面。

Qt的常用控件

Qt中常用的控件有很多,下面列举一些常用的控件,并给出简单的例子:

  1. QPushButton(按钮控件)

QPushButton *button = new QPushButton("Click me", this);
connect(button, &QPushButton::clicked, [=](){
    QMessageBox::information(this, "Message", "Button clicked");
});
  1. QLabel(标签控件)

QLabel *label = new QLabel("Hello World", this);
label->setAlignment(Qt::AlignCenter);
  1. QLineEdit(文本框控件)

QLineEdit *lineEdit = new QLineEdit(this);
lineEdit->setPlaceholderText("Enter your name");
connect(lineEdit, &QLineEdit::returnPressed, [=](){
    QString name = lineEdit->text();
    QMessageBox::information(this, "Message", "Hello " + name);
});
  1. QTextEdit(多行文本框控件)

QTextEdit *textEdit = new QTextEdit(this);
textEdit->setPlaceholderText("Enter your message");
connect(textEdit, &QTextEdit::textChanged, [=](){
    QString message = textEdit->toPlainText();
    qDebug() << message;
});
  1. QComboBox(下拉框控件)

QComboBox *comboBox = new QComboBox(this);
comboBox->addItem("Apple");
comboBox->addItem("Banana");
comboBox->addItem("Cherry");
connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){
    QString fruit = comboBox->itemText(index);
    qDebug() << fruit;
});
  1. QSpinBox(数字框控件)

QSpinBox *spinBox = new QSpinBox(this);
spinBox->setRange(0, 100);
spinBox->setValue(50);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), [=](int value){
    qDebug() << value;
});
  1. QSlider(滑动条控件)

QSlider *slider = new QSlider(Qt::Horizontal, this);
slider->setRange(0, 100);
slider->setValue(50);
connect(slider, &QSlider::valueChanged, [=](int value){
    qDebug() << value;
});

这些控件只是Qt中的一部分,Qt还提供了很多其他的控件,如QCheckBox、QRadioButton、QTableWidget、QTreeWidget等等。这些控件可以帮助我们快速构建各种类型的界面,提高开发效率。

Qt样式表

Qt中的样式表是一种用于定制控件外观的机制,类似于CSS。通过样式表,可以改变控件的颜色、字体、边框等属性,从而实现自定义的外观效果。

以下是一些常用的样式表属性和例子:

  1. background-color:设置控件的背景颜色。例如:QPushButton { background-color: red; }

  2. color:设置控件的前景颜色(即文本颜色)。例如:QLabel { color: blue; }

  3. font-size:设置控件的字体大小。例如:QLineEdit { font-size: 12px; }

  4. border:设置控件的边框。例如:QGroupBox { border: 1px solid black; }

  5. padding:设置控件的内边距(即控件内容与边框之间的距离)。例如:QLineEdit { padding: 5px; }

  6. margin:设置控件的外边距(即控件边框与周围控件之间的距离)。例如:QLabel { margin: 10px; }

这些样式表属性可以组合使用,实现更丰富的外观效果。例如,以下样式表将QPushButton的背景颜色设置为蓝色,边框为圆角,字体为白色,内边距为10px:

QPushButton { background-color: blue; border-radius: 5px; color: white; padding: 10px; }

最后总结

以上的内容都是简单的罗列,具体的内容还需看书本或者视频,如果是复习可以参考此文章,学习C++不是一蹴而就的,是终身的,基础部分十分的重要,需要反复的学习和体会。

 
 
 
 

16f3c5335707baafa62bae35ee8cd351.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

推荐阅读

全面讲解C语言断言函数的应用

C语言入门笔记,推荐收藏!

STM32中常用的C语言知识点,开始复习!

C语言开源项目集锦,足足有十个

戳“阅读原文”我们一起进步

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值