
前言QT可以调用很多第三方的C、C库了解一些基础知识是有必要的。C能兼容大部分C的特性有些是基于设计的考虑不兼容。提“兼容”只是为了指出可察的“延续性”不深入“兼容”这个话题。本篇侧重QT开发者的角度去理解指针和结构体的知识是为类进行铺垫主要是方便QT开发者阅读对于C开发者来说也仅是基础知识。结构体在C语言中结构体是方便开发者自定义数据类型准确的说是“数据聚合器”即把开发者需要的类型按顺序集合在一个地址空间中。在C中基于设计哲学OOP结构体就是一个特殊的类可以理解为一个轻量级的类特殊点是类成员默认为私有类型结构体默认为公开类型。特性结构体(struct)类(class)成员变量✅✅成员函数✅✅继承✅✅多态✅✅模板✅✅访问控制✅✅默认访问权限publicprivateC风格的结构体用法结构体与专属的操作是分类的#include stdio.h #include stdlib.h #include string.h #include stddef.h typedef struct Buffer{ char* data; size_t size; } stBuffer; void buffer_init(stBuffer* buf, size_t size) { if (buf NULL) return; buf-data (char*)malloc(size); if (buf-data ! NULL) { buf-size size; memset(buf-data, 0, size); } else { buf-size 0; } } void buffer_free(stBuffer* buf) { if (buf NULL) return; if (buf-data ! NULL) { free(buf-data); buf-data NULL; } buf-size 0; } void buffer_copy(stBuffer* dst, const stBuffer* src) { if (dst NULL || src NULL || src-data NULL) return; if (dst-data ! NULL) { free(dst-data); } dst-data (char*)malloc(src-size); if (dst-data ! NULL) { memcpy(dst-data, src-data, src-size); dst-size src-size; } else { dst-size 0; } } void print_buffer(const stBuffer* buf, const char* name) { printf(%s: data%p, size%zu\n, name, (void*)buf-data, buf-size); if (buf-data ! NULL) { printf(Content: ); for (size_t i 0; i buf-size i 10; i) { printf(%02x , (unsigned char)buf-data[i]); } printf(\n); } } int main() { printf(Buffer Management System Test\n); printf(\n\n); stBuffer buf1; printf(Test 1: Initialize buffer\n); buffer_init(buf1, 16); print_buffer(buf1, buf1); printf(\n); stBuffer buf2; printf(Test 2: Copy buffer\n); buffer_copy(buf2, buf1); print_buffer(buf2, buf2); printf(\n); printf(Test 3: Write data to buffer\n); if (buf1.data ! NULL) { strcpy(buf1.data, Hello Buffer!); printf(buf1 content: %s\n, buf1.data); } printf(\n); printf(Test 4: Deep copy with data\n); stBuffer buf3; buffer_copy(buf3, buf1); print_buffer(buf3, buf3); printf(buf3 content: %s\n, buf3.data); printf(\n); printf(Test 5: Free buffers\n); buffer_free(buf1); buffer_free(buf2); buffer_free(buf3); print_buffer(buf1, buf1 after free); print_buffer(buf2, buf2 after free); print_buffer(buf3, buf3 after free); return 0; }C风格的结构体用法#include iostream #include vector #include string #include cstring struct Buffer { std::vectorchar data; // 默认构造函数 Buffer() default; // 带大小参数的构造函数 Buffer(size_t size) : data(size) {} // 从C字符串构造 Buffer(const char* str) { if (str) { size_t len strlen(str); data.resize(len); memcpy(data.data(), str, len); } } // 从std::string构造 Buffer(const std::string str) { data.resize(str.size()); memcpy(data.data(), str.c_str(), str.size()); } // 复制数据函数 void copyFrom(const Buffer other) { data other.data; } // 清空缓冲区 void clear() { data.clear(); } // 打印缓冲区信息 void printInfo(const std::string name) const { std::cout name - 大小: data.size(); if (!data.empty()) { std::cout , 内容: ; // 显示十六进制数据 for (size_t i 0; i data.size() i 15; i) { printf(%02x , static_castunsigned char(data[i])); } if (data.size() 15) { std::cout ...; } } std::cout std::endl; } }; int main() { std::cout 1. 不同构造函数: std::endl; Buffer buf1(10); // 指定大小 Buffer buf2(Hello C Buffer!); // 从C字符串构造 Buffer buf3(std::string(From std::string)); // 从std::string构造 Buffer buf4; // 默认构造 std::cout 2. 复制操作: std::endl; buf4.copyFrom(buf2); buf4.printInfo(buf4 复制后); std::cout std::endl; std::cout 3. 清空缓冲区: std::endl; buf2.clear(); return 0; }这里先不讲解C的结构体里面为什么能加入函数在类中讲解。指针“慎用”、“越界”、“安全释放”初学C时没少被吓唬。为了“高效”数据存储/读取、运算、排序中都有指针的影子在C中就很少因为C做了很多封装比如常用的STL容器迭代器就是一个节点指针new一个类型/结构体/类对象底层封装了malloc。在使用第三方库时通常都会接触到指针函数和函数指针这两个容易搞混下面详细讲解。指针函数字面意思返回指针的函数。QT调用第三方库时有不少的机会遇到。以下代码我是之前分享的的博文https://blog.csdn.net/liangyuna8787/article/details/144730835?spm1001.2014.3001.5502【QT常用技术讲解】excel表格处理两种方式QAxObject和qtxlsx的代码片段#include xlsxdocument.h #include xlsxworksheet.h void MainWindow::saveexcel(QTableWidget *tableWidget, const QString fileName) { QXlsx::Document xlsx; QXlsx::Worksheet *worksheet xlsx.currentWorksheet(); // 设置表头和数据 for (int row 0; row tableWidget-rowCount(); row) { for (int col 0; col tableWidget-columnCount(); col) { QTableWidgetItem *item tableWidget-item(row, col); if (item) { worksheet-write(row 1, col 1, item-text()); } else { worksheet-write(row 1, col 1, ); } } } //合并表头 xlsx.mergeCells(QXlsx::CellRange(1, 1, 1, tableWidget-columnCount())); // 保存到文件 xlsx.saveAs(fileName); }可以看到有两个函数返回了指针一般涉及层级、节点的库都会用指针进行高效操作。函数指针它是一个指向某种类型函数的指针变量即这个指针变量指向某个类型编译器会检测类型是否匹配的函数。通俗的讲它首先是一个变量因为具备指针的“借尸还魂”的指向特性可以指向某个代码片段。比较常见的案例是C、C动态库的函数调用以下分享Linux下C、C动态库调用的案例C函数指针--动态库调用// 动态库文件mathlib.c int add(int a, int b) { return a b; } int multiply(int a, int b) { return a * b; } //编译命令gcc -fPIC -shared -o libmath.so mathlib.c用dlopen方式调用动态库以下提供两种函数指针类型定义的方法// main.c #include stdio.h #include stdlib.h #include dlfcn.h // 必须包含此头文件 // 函数指针类型定义与动态库中的函数签名一致 int (*add)(int, int); typedef int (*multiply_func_t)(int, int); int main() { void *handle; multiply_func_t multiply; char *error; // 打开动态库 handle dlopen(./libmath.so, RTLD_LAZY); if (!handle) { fprintf(stderr, dlopen error: %s\n, dlerror()); exit(EXIT_FAILURE); } // 清除之前的错误 dlerror(); // 获取 add 函数地址 *(void **)(add) dlsym(handle, add); error dlerror(); if (error ! NULL) { fprintf(stderr, dlsym add error: %s\n, error); dlclose(handle); exit(EXIT_FAILURE); } // 获取 multiply 函数地址 *(void **)(multiply) dlsym(handle, multiply); error dlerror(); if (error ! NULL) { fprintf(stderr, dlsym multiply error: %s\n, error); dlclose(handle); exit(EXIT_FAILURE); } // 调用函数 printf(add(3, 5) %d\n, add(3, 5)); printf(multiply(4, 6) %d\n, multiply(4, 6)); // 关闭动态库 dlclose(handle); return 0; }C函数指针--动态库调用动态库不仅仅可以传递函数类也可以传递以及直接传递类中的函数。编写动态库时需要额外加上extern C {}之前的博文分享传递类和函数的样例#pragma once #include iostream class MyClass { public: MyClass(); ~MyClass(); void printMessage(const char* msg); }; //普通函数需要使用extern C声明外部才可调用 #ifdef __cplusplus extern C { #endif MyClass* createMyClass(); // 创建对象 void destroyMyClass(MyClass*); // 销毁对象 #ifdef __cplusplus } #endif详细讲解请查看博文【Linux C/C开发】编译及引用so动态库注意了以上分享函数指针的样例是让你感知到指针强大的“跨域”引用能力能更好的理解类。类从C结构体的延伸类是面向对象编程oop的核心设计C本身也是为了改进C语言从底层语言向高级语言迈进调侃~。前面已经分享了在C结构体是一个特殊的类。从C语言的角度来看也可以理解为C中的类是从结构体延伸出去的成熟体。这个是“C语言的角度”了解C语言基础知识的人都知道C是没有类的但结构体函数指针是可以看出类的雏形下面提供个代码案例#include stdio.h #include stdlib.h #include string.h // 模拟一个矩形类 typedef struct Rectangle { // 数据成员 double width; double height; // 函数指针模拟成员函数 double (*area)(struct Rectangle* self); double (*perimeter)(struct Rectangle* self); void (*setSize)(struct Rectangle* self, double w, double h); void (*display)(struct Rectangle* self); } Rectangle; // 成员函数实现 double rectangle_area(Rectangle* self) { return self-width * self-height; } double rectangle_perimeter(Rectangle* self) { return 2 * (self-width self-height); } void rectangle_setSize(Rectangle* self, double w, double h) { if (w 0) self-width w; if (h 0) self-height h; } void rectangle_display(Rectangle* self) { printf(矩形: 宽%.2f, 高%.2f, 面积%.2f, 周长%.2f\n, self-width, self-height, self-area(self), self-perimeter(self)); } // 构造函数 Rectangle* Rectangle_create(double width, double height) { Rectangle* rect (Rectangle*)malloc(sizeof(Rectangle)); if (rect) { rect-width width; rect-height height; // 绑定函数指针 rect-area rectangle_area; rect-perimeter rectangle_perimeter; rect-setSize rectangle_setSize; rect-display rectangle_display; } return rect; } // 析构函数 void Rectangle_destroy(Rectangle* rect) { if (rect) { free(rect); } } //---↑↑↑↑↑--以上是结构体矩形类相关定义-----↑↑↑↑↑ //---↓↓↓↓↓--以下模拟继承特性--------------↓↓↓↓↓ // 模拟继承彩色矩形继承自矩形 typedef struct ColoredRectangle { Rectangle base; // 基类模拟继承 char color[20]; // 新增的函数指针 void (*setColor)(struct ColoredRectangle* self, const char* color); void (*displayColor)(struct ColoredRectangle* self); } ColoredRectangle; // 子类特有的函数 void coloredRectangle_setColor(ColoredRectangle* self, const char* color) { strncpy(self-color, color, sizeof(self-color) - 1); self-color[sizeof(self-color) - 1] \0; } void coloredRectangle_displayColor(ColoredRectangle* self) { printf(彩色); self-base.display((Rectangle*)self); // 调用基类方法 printf(颜色: %s\n, self-color); } // 子类构造函数 ColoredRectangle* ColoredRectangle_create(double width, double height, const char* color) { ColoredRectangle* crect (ColoredRectangle*)malloc(sizeof(ColoredRectangle)); if (crect) { // 初始化基类部分 crect-base.width width; crect-base.height height; // 绑定基类函数指针 crect-base.area rectangle_area; crect-base.perimeter rectangle_perimeter; crect-base.setSize rectangle_setSize; crect-base.display rectangle_display; // 初始化子类部分 strncpy(crect-color, color, sizeof(crect-color) - 1); crect-color[sizeof(crect-color) - 1] \0; // 绑定子类函数指针 crect-setColor coloredRectangle_setColor; crect-displayColor coloredRectangle_displayColor; } return crect; } // 子类析构函数 void ColoredRectangle_destroy(ColoredRectangle* crect) { if (crect) { free(crect); } } //---↑↑↑↑↑--以上模拟继承特性--------------↑↑↑↑↑ int main(){ // 1. 创建和使用矩形对象 printf(1. 矩形类使用:\n); Rectangle* rect Rectangle_create(10.0, 5.0); rect-display(rect); rect-setSize(rect, 8.0, 6.0); printf(修改大小后: ); rect-display(rect); printf(面积: %.2f\n, rect-area(rect)); printf(\n); // 2. 继承使用 printf(2. 继承使用:\n); ColoredRectangle* credRect ColoredRectangle_create(12.0, 8.0, 红色); credRect-displayColor(credRect); credRect-setColor(credRect, 蓝色); credRect-base.setSize((Rectangle*)credRect, 15.0, 10.0); printf(修改后: ); credRect-displayColor(credRect); printf(\n); // 3. 资源清理 printf(4. 资源清理:\n); Rectangle_destroy(rect); ColoredRectangle_destroy(credRect); return 0; }C语言是面向过程的编程语言C是面向对象的编程语言。不只是设计理念语法、编译器也不一样。以下是几个演变过程从C的结构体到C的类C语言的结构体只能包含数据成员而C的结构体可以包含成员函数、访问控制public、private、protected、继承、虚函数等。虚函数表vtable为了实现多态C引入了虚函数。每个包含虚函数的类或结构体都会有一个虚函数表这是一个函数指针数组每个虚函数对应一个指针。每个对象会有一个隐藏的指针vptr指向这个表。当调用虚函数时通过vptr找到虚函数表再通过偏移量调用正确的函数。成员函数非虚成员函数在编译时就可以确定地址因此不需要通过指针间接调用。它们与普通函数类似只是隐含了一个指向对象的指针this指针自动在函数参数列表开头添加一个隐藏参数ClassType* thisQT的信号-槽机制其实也是在维护一个”表“某个地方发出信号之后信号就遍历这个”信号-槽“表匹配上信号的就触发槽函数的调用。虚函数表以及指向对象的指针this指针机制都是C为了让类准确的找到虚函数/成员函数。以上只是从映射表的逻辑来描述C结构体和C的关联性辅助理解并非是全部执行逻辑。类的特性以上是与C有关的知识下面记录下QT开发中使用到的类特性。类的封装类默认是private访问控制即私有性质外部无法直接调用。除了构造函数需要通过显示接口对外暴露调用尽可能避免外部代码直接修改内部状态。比如myclass类有一个成员变量bool m_status;如果设置为public访问就会被外部代码直接引用并修改可以这样操作但是不安全违背封装性通常是提供一个对外暴露的接口函数bool getmyclassstatus() const;来获取支持如果需要修改此值也是提供对外暴露的接口函数 void setstatus(bool);来使用。private访问控制不代表不能访问友元(friend)机制就是一个特例机制。类的继承定义一个新类派生类 / 子类基于已有类基类 / 父类从而复用代码、扩展功能、实现多态。//常用的单继承 class MainWindow : public QMainWindow{ }; //可以多继承 class Document : public Printable, public Serializable { };基类中的访问权限继承方式在派生类中的访问权限publicpublicpublicprotectedpublicprotectedprivatepublic不可访问publicprotectedprotectedprotectedprotectedprotectedprivateprotected不可访问publicprivateprivateprotectedprivateprivateprivateprivate不可访问类的构造、析构调用规则构造函数的主要职责是初始化对象。它在对象内存分配完成后、对象正式可用之前自动执行。#include iostream #include vector using namespace std; // 自定义测试类打印所有构造/析构的调用信息 class MyClass { private: int val; public: // 普通构造函数 MyClass(int v 0) : val(v) { cout [调用普通构造函数] val val endl; } // 拷贝构造函数 MyClass(const MyClass other) { val other.val; cout [调用拷贝构造函数] val val endl; } // 移动构造函数 MyClass(MyClass other) noexcept { val other.val; other.val -1; cout [调用移动构造函数] val val endl; } // 析构函数 ~MyClass() { cout [调用析构函数] val val endl; } }; // ---------------- 1. 全局对象 ---------------- MyClass globalObj(100); // main执行前就会调用构造函数 void testStackObj() { cout \n--- 进入testStackObj函数准备定义栈上对象 --- endl; // 场景1栈上局部对象执行到这行代码时立即调用构造 MyClass stackObj(1); cout --- 即将离开testStackObj作用域栈对象马上析构 --- endl; } void testStaticLocalObj() { // 场景3函数内静态局部对象仅第一次执行到该行时调用构造 static MyClass staticObj(30); } // 按值传参会触发拷贝构造 void passByValue(MyClass obj) { cout --- 按值传参的函数内部执行 --- endl; } // 返回局部对象触发移动/拷贝构造 MyClass returnObj() { MyClass temp(20); return temp; } int main() { cout main函数正式开始执行 endl; // 场景1测试栈上对象 testStackObj(); // 场景2堆上动态对象 cout \n--- 准备执行new创建堆对象 --- endl; MyClass* heapObj new MyClass(2); cout --- 准备执行delete释放堆对象 --- endl; delete heapObj; // 手动调用析构 // 场景3测试静态局部对象 testStaticLocalObj(); testStaticLocalObj(); // 第二次调用验证静态对象只构造一次 // 场景4临时对象、拷贝/移动场景 cout \n--- 按值传递参数场景 --- endl; MyClass a(5); passByValue(a); // 场景5:函数返回局部对象场景 MyClass ret returnObj(); //场景6 vector扩容触发移动构造场景 vectorMyClass vec; // 预先预留空间可以避免扩容这里故意不预留来触发扩容 vec.emplace_back(10); vec.emplace_back(20); return 0; }析构函数的主要职责是清理资源如释放内存、关闭文件句柄等。它在对象生命周期结束时自动执行用户通常不能手动调用。析构函数的自调用时机也受程序的作用域影响下面的表格是列举上面代码例子的析构函数调用时间点对象析构触发时机// 场景1testStackObj()函数执行完、离开testStackObj作用域的瞬间就调用析构函数进行销毁// 场景2new MyClass(2)手动执行delete heapObj的时候立刻调用析构// 场景3static MyClass在main()函数执行完return 0之后、操作系统正式回收进程资源之前由C运行时的全局退出清理逻辑统一调用它的析构函数。// 场景4passByValue(MyClass obj)passByValue函数执行完、返回的瞬间就销毁main里的局部对象a、ret、局部容器vec比如场景5和场景6在main()函数执行完return 0之后、操作系统正式回收进程资源之前由C运行时的全局退出清理逻辑统一调用它的析构函数。调用顺序上面已经介绍了单个类的构造函数和析构函数的调用规则这也是它们在类中的声明周期。下面介绍的是在类继承的情况下构造函数和析构函数的调用顺序。单继承顺序构造时子类对象包含基类子对象所以必须先把基类部分初始化好子类才能安全使用基类成员。析构时反过来子类资源通常依赖基类资源所以必须先清理子类自己的资源最后再清理基类部分。所以顺序如下构造先基类后子类。析构先子类后基类。也可以计算机的栈的工作机制来理解构造时先把基类构造资源压入栈再把子类构造资源压入栈析构时只有先把子类构造资源从栈中释放掉才能继续把基类构造资源释放掉。#include iostream class Base { public: Base() { std::cout Base 构造\n; } ~Base() { std::cout Base 析构\n; } }; class Derived : public Base { public: Derived() { std::cout Derived 构造\n; } ~Derived() { std::cout Derived 析构\n; } }; int main() { Derived d; } /*打印输出如下*** Base 构造 Derived 构造 Derived 析构 Base 析构 ****************/如果是多层级继承也是上面的规则如下class Derived : public Base1, public Base2 { ... }; //构造Base1 → Base2 → Derived //析构Derived → Base2 → Base1类的多态使用统一的接口处理不同的数据类型。运行时多态动态多态虚函数机制QT开发中经常接触的有重写虚函数比如事件过滤器eventFilter()是QObject类中已经声明了虚函数virtual bool eventFilter(QObject *watched, QEvent *event);默认实现是返回false表示不处理事件继续传递如果需要拦截某个对象的事件需要重写overrideeventFilter()protected: bool eventFilter(QObject *watched, QEvent *event) override;//重写事件过滤器在基类中virtual 标明的虚函数目的规范接口的统一入口引用的类需要自己具体实现业务逻辑。继承多态可以有以下一个现象A类继承了base类重写了event函数B类继承了A类B类也可以重写event函数。有一种多重套娃的感觉但是如果在A类中重写event函数时加上final就可以防止B类进一步重写。class A { // final防止进一步重写 void event() override final { cout Derived(final) endl; } }编译时多态静态多态函数重载、运算符重载、模板函数重载同一函数名参数类型不同class Calculator { public: // 同一函数名不同参数列表 int add(int a, int b) { cout 整数加法: ; return a b; } double add(double a, double b) { cout 浮点数加法: ; return a b; } string add(const string a, const string b) { cout 字符串连接: ; return a b; } int add(int a, int b, int c) { cout 三个整数加法: ; return a b c; } }; // 使用 Calculator calc; cout calc.add(5, 3) endl; // 整数加法: 8 cout calc.add(2.5, 3.7) endl; // 浮点数加法: 6.2 cout calc.add(Hello, World) endl; // 字符串连接: HelloWorld cout calc.add(1, 2, 3) endl; // 三个整数加法: 6运算符重载对常用的运算符、”-“、”“等进行重载class Complex { private: double real, imag; public: Complex(double r 0, double i 0) : real(r), imag(i) {} // 运算符重载 Complex operator(const Complex other) const { return Complex(real other.real, imag other.imag); } Complex operator-(const Complex other) const { return Complex(real - other.real, imag - other.imag); } // 关系运算符重载 bool operator(const Complex other) const { return real other.real imag other.imag; } // 输出运算符重载友元函数 friend ostream operator(ostream os, const Complex c) { os c.real c.imag i; return os; } }; // 使用 Complex c1(3, 4), c2(1, 2); Complex c3 c1 c2; // 多态运算符根据操作数类型执行不同操作 cout c3 endl; // 输出: 4 6i模板需要进行多种类型的运算时抽象出一个模板比函数重载更好管理。// 没有模板 - 需要为每种类型重载函数 int max(int a, int b) { return (a b) ? a : b; } double max(double a, double b) { return (a b) ? a : b; } string max(const string a, const string b) { return (a b) ? a : b; } // 有模板 - 一个模板处理所有类型 templatetypename T T max(T a, T b) { return (a b) ? a : b; }类中的元素也可以做成模板templatetypename T class Stack { private: vectorT elements; public: void push(const T value) { elements.push_back(value); } T pop() { if (elements.empty()) { throw out_of_range(Stack is empty); } T value elements.back(); elements.pop_back(); return value; } T top() const { if (elements.empty()) { throw out_of_range(Stack is empty); } return elements.back(); } bool empty() const { return elements.empty(); } size_t size() const { return elements.size(); } }; // 使用 int main() { Stackint intStack; intStack.push(1); intStack.push(2); intStack.push(3); while (!intStack.empty()) { cout intStack.pop() ; // 输出: 3 2 1 } cout endl; Stackstring stringStack; stringStack.push(World); stringStack.push(Hello); cout stringStack.pop() stringStack.pop() endl; // Hello World return 0; }