Visual C++实战编程100例(第2版)完整源码解析

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《Visual C++实战编程100例(第2版)》是一本专注于Visual C++编程实践的技术书籍,结合完整源代码帮助读者掌握C++在Windows平台下的开发技巧。内容涵盖C++基础语法、面向对象编程、标准模板库(STL)应用、数据库连接(ODBC/ADO)、Windows API使用、设计模式实践、调试优化技巧等多个方面。通过100个实例项目,读者可系统学习Visual C++在真实开发场景中的应用,提升代码质量与项目实战能力。
Visual+C++实效编程百例(第2版)+源代码.7z

1. Visual C++基础语法详解与实例

1.1 变量定义与数据类型

在Visual C++中,变量的定义是程序开发的基础。C++支持多种内置数据类型,包括整型( int )、浮点型( float double )、字符型( char )、布尔型( bool )等。定义变量时需指定其类型和名称,例如:

int age = 25;           // 整型变量
double salary = 5000.5; // 双精度浮点型
char grade = 'A';       // 字符型
bool isStudent = true;  // 布尔型

上述代码中,变量被赋予了初始值。在Visual Studio中编译运行后,可通过调试器查看变量值的变化,理解其在内存中的存储方式。变量命名应遵循命名规范,如使用有意义的英文单词,避免关键字等。

在后续章节中,我们将通过运算符与表达式来操作这些变量,构建更复杂的逻辑结构。

2. C++面向对象编程核心概念与实现

C++是一门面向对象的编程语言,其核心特性包括类(Class)、对象(Object)、封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。面向对象编程(OOP)的核心理念是将数据与操作数据的方法封装为一个整体——即类。本章将从类与对象的基本概念入手,逐步深入探讨封装、继承与多态的实现机制,并通过实际代码示例展示其应用。此外,本章还将介绍运算符重载与友元函数等高级特性,并以构建简单图形类库为例,帮助读者全面掌握C++面向对象编程的核心思想与实现技巧。

2.1 类与对象的基本概念

类与对象是面向对象编程的基础。类是对某一类事物的抽象描述,而对象则是类的具体实例。类中包含数据成员(属性)和成员函数(方法)。本节将从类的定义、成员变量的声明与访问、构造函数与析构函数的作用等方面进行深入讲解,并结合示例代码说明其具体实现方式。

2.1.1 类的定义与成员变量

在C++中,类使用关键字 class struct 来定义。两者的主要区别在于默认访问权限: class 的成员默认为私有(private),而 struct 的成员默认为公有(public)。类的定义通常包括数据成员(变量)和成员函数(方法)。

以下是一个简单的类定义示例:

class Rectangle {
private:
    int width;
    int height;

public:
    void setWidth(int w);
    void setHeight(int h);
    int getArea();
};
代码逻辑分析:
  • class Rectangle :定义了一个名为 Rectangle 的类。
  • private: :表示接下来的成员变量为私有,外部不可直接访问。
  • int width; int height; :类的两个私有数据成员,用于存储矩形的宽度和高度。
  • public: :表示接下来的成员函数为公有,可在类的外部调用。
  • void setWidth(int w); :设置宽度的成员函数声明。
  • void setHeight(int h); :设置高度的成员函数声明。
  • int getArea(); :计算矩形面积的成员函数声明。
成员函数的实现:
void Rectangle::setWidth(int w) {
    width = w;
}

void Rectangle::setHeight(int h) {
    height = h;
}

int Rectangle::getArea() {
    return width * height;
}
  • Rectangle::setWidth :使用作用域解析运算符 :: 来定义类 Rectangle 的成员函数。
  • width = w; :将传入的参数 w 赋值给类的私有成员变量 width
  • getArea() 函数返回矩形的面积。
对象的创建与使用:
#include <iostream>
using namespace std;

int main() {
    Rectangle rect;
    rect.setWidth(5);
    rect.setHeight(10);
    cout << "Area: " << rect.getArea() << endl;
    return 0;
}
  • Rectangle rect; :创建 Rectangle 类的一个对象 rect
  • rect.setWidth(5); :通过对象调用成员函数设置宽度。
  • rect.setHeight(10); :设置高度。
  • cout << "Area: " << rect.getArea() << endl; :输出面积。

2.1.2 构造函数与析构函数的作用

构造函数和析构函数是C++中用于对象生命周期管理的重要机制。构造函数在对象创建时自动调用,用于初始化对象;析构函数在对象销毁时自动调用,用于释放对象占用的资源。

构造函数的定义与使用:

构造函数的名称必须与类名相同,没有返回类型,可以重载。

class Rectangle {
private:
    int width;
    int height;

public:
    // 构造函数
    Rectangle(int w, int h) {
        width = w;
        height = h;
    }

    int getArea() {
        return width * height;
    }
};
使用构造函数创建对象:
int main() {
    Rectangle rect(5, 10);
    cout << "Area: " << rect.getArea() << endl;
    return 0;
}
  • Rectangle rect(5, 10); :调用构造函数初始化对象。
析构函数的定义与使用:

析构函数用于释放对象所占用的资源,通常用于释放动态分配的内存。析构函数没有参数,不能重载。

class Rectangle {
private:
    int width;
    int height;

public:
    Rectangle(int w, int h) {
        width = w;
        height = h;
        cout << "Constructor called" << endl;
    }

    ~Rectangle() {
        cout << "Destructor called" << endl;
    }

    int getArea() {
        return width * height;
    }
};
输出示例:
Constructor called
Area: 50
Destructor called
  • 构造函数在对象创建时输出提示。
  • 析构函数在对象销毁时(如程序结束)输出提示。
构造函数与析构函数总结:
特性 构造函数 析构函数
名称 与类名相同 ~类名
返回类型
参数 可有 不可有
调用时机 对象创建时 对象销毁时
作用 初始化对象状态 释放对象资源
可否重载 可以 不可以

通过构造函数和析构函数的配合,C++实现了对象生命周期的自动管理,为面向对象编程提供了坚实的基础。在后续章节中,我们将进一步探讨封装、继承与多态的实现机制,深入理解C++面向对象编程的核心思想。

3. STL容器(vector/list/set等)实战应用

STL(Standard Template Library,标准模板库)是C++中最强大、最实用的库之一,它提供了丰富的容器、算法和迭代器,极大地提升了程序开发效率与代码质量。本章将深入探讨STL中常用容器的使用方法,包括vector、list、set、map等,并通过实际案例展示其在项目开发中的应用。

3.1 常用STL容器概述

STL容器主要分为两大类:序列式容器(如vector、list、deque)和关联式容器(如set、map、multiset、multimap)。每种容器都有其特定的使用场景和性能特点。

3.1.1 vector与list的特性对比

vector list 是两个最常用的序列式容器,它们在内存结构、插入删除效率、访问方式等方面有显著区别。

特性 vector list
内存结构 连续存储 链表结构
插入/删除效率 尾部高效,中间/头部效率低 任意位置高效
随机访问 支持(O(1)) 不支持(O(n))
迭代器失效 插入元素超过容量时会失效 插入元素不影响其他迭代器
内存开销 较低 较高(每个节点需维护前后指针)

代码示例:vector 与 list 的基本操作

#include <iostream>
#include <vector>
#include <list>

int main() {
    // vector 示例
    std::vector<int> vec = {1, 2, 3};
    vec.push_back(4);  // 尾部插入
    vec.insert(vec.begin() + 2, 5);  // 在索引2前插入5

    std::cout << "vector elements: ";
    for (int x : vec)
        std::cout << x << " ";
    std::cout << std::endl;

    // list 示例
    std::list<int> lst = {10, 20, 30};
    lst.push_back(40);  // 尾部插入
    lst.insert(std::next(lst.begin(), 1), 25);  // 在索引1前插入25

    std::cout << "list elements: ";
    for (int x : lst)
        std::cout << x << " ";
    std::cout << std::endl;

    return 0;
}

逻辑分析与参数说明:

  • vector 使用 push_back 在尾部添加元素,时间复杂度为 O(1)。
  • insert(vec.begin() + 2, 5) 在索引 2 前插入元素,时间复杂度为 O(n),因为需要移动元素。
  • list insert 操作效率更高,因为链表结构允许在任意位置插入元素而无需移动其他节点。
  • std::next(lst.begin(), 1) 获取从起始位置后移1位的迭代器。

3.1.2 set与map的排序机制

set map 是基于红黑树实现的关联式容器,支持自动排序和快速查找。

  • set<T> :存储唯一的 T 类型元素,自动排序。
  • map<K, V> :存储键值对,键唯一,自动按键排序。

代码示例:set 与 map 的基本使用

#include <iostream>
#include <set>
#include <map>

int main() {
    // set 示例
    std::set<int> s;
    s.insert(5);
    s.insert(3);
    s.insert(7);
    s.insert(3);  // 重复元素不会插入

    std::cout << "set elements: ";
    for (int x : s)
        std::cout << x << " ";
    std::cout << std::endl;

    // map 示例
    std::map<std::string, int> m;
    m["Alice"] = 25;
    m["Bob"] = 30;
    m["Charlie"] = 28;

    std::cout << "map elements: " << std::endl;
    for (auto& kv : m)
        std::cout << kv.first << " => " << kv.second << std::endl;

    return 0;
}

逻辑分析与参数说明:

  • set 会自动排序并去重,因此 s.insert(3) 被调用两次只保留一个。
  • map 按照键的字典序排序(字符串比较),并支持通过 [] 运算符快速赋值和访问。
  • 使用 auto& kv 遍历 map 时, kv.first 是键, kv.second 是值。

mermaid 流程图:set 与 map 的底层结构对比

graph TD
    A[set<int>] --> B[红黑树结构]
    B --> C[元素自动排序]
    B --> D[元素不可重复]

    E[map<string, int>] --> F[红黑树结构]
    F --> G[键自动排序]
    F --> H[键不可重复]
    H --> I[值可重复]

3.2 容器的操作与内存管理

STL容器提供了丰富的操作接口,包括插入、删除、遍历以及动态扩容等。理解这些操作的内部机制有助于优化程序性能。

3.2.1 容器的插入、删除与遍历操作

所有容器都支持基本的插入(insert/push_back)、删除(erase)和遍历(迭代器)操作。

代码示例:vector 插入与删除操作

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 插入元素
    vec.insert(vec.begin() + 2, 10);  // 在索引2前插入10

    // 删除元素
    vec.erase(vec.begin() + 3);  // 删除索引3的元素

    std::cout << "vector after insert and erase: ";
    for (int x : vec)
        std::cout << x << " ";
    std::cout << std::endl;

    return 0;
}

逻辑分析与参数说明:

  • insert(vec.begin() + 2, 10) :在索引 2 前插入元素 10,此时 vector 中的元素变为 [1, 2, 10, 3, 4, 5]。
  • erase(vec.begin() + 3) :删除索引 3 的元素,即原值 3,此时结果为 [1, 2, 10, 4, 5]。
  • begin() 返回指向容器起始的迭代器, begin() + n 表示移动 n 位后的迭代器。

代码示例:list 的删除与遍历

#include <iostream>
#include <list>

int main() {
    std::list<int> lst = {10, 20, 30, 40, 50};

    // 删除值为30的节点
    lst.remove(30);

    std::cout << "list after remove: ";
    for (int x : lst)
        std::cout << x << " ";
    std::cout << std::endl;

    return 0;
}

逻辑分析与参数说明:

  • remove(30) 会删除所有等于 30 的元素(适用于 list 特有的成员函数)。
  • 遍历使用范围 for 循环,适用于所有容器。

3.2.2 容器动态扩容与内存释放

vector string 等容器在元素数量超过当前容量时会自动扩容,扩容过程会重新分配内存并复制旧数据。

代码示例:vector 扩容行为分析

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    std::cout << "Initial capacity: " << vec.capacity() << std::endl;

    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
        std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
    }

    return 0;
}

逻辑分析与参数说明:

  • capacity() 返回当前分配的内存大小(可容纳的元素数)。
  • push_back() 在尾部添加元素,当 size 超过 capacity 时,容器自动扩容,通常是按 2 倍增长。
  • 输出示例:
    Initial capacity: 0 Size: 1, Capacity: 1 Size: 2, Capacity: 2 Size: 3, Capacity: 4 Size: 4, Capacity: 4 Size: 5, Capacity: 8 ...

表格:vector 扩容行为分析

操作次数 元素数 容量
1 1 1
2 2 2
3 3 4
4 4 4
5 5 8
6 6 8
7 7 8
8 8 8
9 9 16
10 10 16

3.3 自定义类型在STL容器中的使用

在实际开发中,往往需要将自定义类对象存入 STL 容器中。这要求我们正确实现比较操作符和拷贝构造函数等。

3.3.1 重载比较运算符以支持set/map

set map 要求元素可排序,因此对于自定义类型,必须重载 < 运算符。

代码示例:自定义类 Student 支持 set 存储

#include <iostream>
#include <set>
#include <string>

class Student {
public:
    std::string name;
    int age;

    Student(std::string n, int a) : name(n), age(a) {}

    // 重载小于运算符用于排序
    bool operator<(const Student& other) const {
        return age < other.age;  // 按年龄排序
    }
};

int main() {
    std::set<Student> students;
    students.insert(Student("Alice", 20));
    students.insert(Student("Bob", 18));
    students.insert(Student("Charlie", 22));

    for (auto& s : students) {
        std::cout << s.name << " (" << s.age << ")\n";
    }

    return 0;
}

逻辑分析与参数说明:

  • operator< 是 set 排序的依据,这里我们以 age 为排序字段。
  • 如果不重载比较运算符,编译会报错,因为 set 无法比较两个 Student 对象。

3.3.2 容器中对象生命周期管理

STL容器存储的是对象的拷贝,因此必须确保类的拷贝构造函数和析构函数正确实现。

代码示例:Student 类的深拷贝与资源管理

#include <iostream>
#include <vector>

class Student {
public:
    char* name;

    Student(const char* n) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
    }

    // 拷贝构造函数
    Student(const Student& other) {
        name = new char[strlen(other.name) + 1];
        strcpy(name, other.name);
    }

    // 析构函数
    ~Student() {
        delete[] name;
    }
};

int main() {
    std::vector<Student> vec;
    vec.push_back(Student("Alice"));  // 临时对象拷贝入容器
    vec.push_back(Student("Bob"));

    return 0;
}

逻辑分析与参数说明:

  • push_back 会调用拷贝构造函数,将临时对象拷贝进 vector。
  • 必须实现深拷贝,避免多个对象共享同一块内存导致多次释放。
  • 如果不实现拷贝构造函数,系统默认的浅拷贝会导致析构时内存错误。

3.4 容器实战案例:学生信息管理系统

本节将通过一个完整的实战项目,展示如何使用 STL 容器构建一个简单的学生信息管理系统。

3.4.1 使用vector存储学生数据

我们将使用 vector<Student> 来存储学生信息,并实现添加、显示等功能。

代码示例:学生信息管理系统(使用 vector)

#include <iostream>
#include <vector>
#include <string>

struct Student {
    std::string name;
    int age;
    double score;
};

int main() {
    std::vector<Student> students;

    // 添加学生
    students.push_back({"Alice", 20, 85.5});
    students.push_back({"Bob", 22, 90.0});
    students.push_back({"Charlie", 19, 78.0});

    // 显示学生信息
    std::cout << "Student List:\n";
    for (const auto& s : students) {
        std::cout << "Name: " << s.name << ", Age: " << s.age << ", Score: " << s.score << "\n";
    }

    return 0;
}

逻辑分析与参数说明:

  • 使用结构体 Student 封装学生信息。
  • 使用 vector 存储多个学生对象,便于动态扩展。
  • 范围 for 循环遍历 vector,输出学生信息。

3.4.2 利用map实现快速查询

为了提高查询效率,我们可以使用 map<std::string, Student> 以学生姓名为键进行快速查找。

代码示例:使用 map 实现学生查询

#include <iostream>
#include <map>
#include <string>

struct Student {
    std::string name;
    int age;
    double score;
};

int main() {
    std::map<std::string, Student> studentMap;

    // 添加学生
    studentMap["Alice"] = {"Alice", 20, 85.5};
    studentMap["Bob"] = {"Bob", 22, 90.0};
    studentMap["Charlie"] = {"Charlie", 19, 78.0};

    // 查询学生
    std::string name;
    std::cout << "Enter student name to search: ";
    std::cin >> name;

    auto it = studentMap.find(name);
    if (it != studentMap.end()) {
        const Student& s = it->second;
        std::cout << "Found: " << s.name << ", Age: " << s.age << ", Score: " << s.score << "\n";
    } else {
        std::cout << "Student not found.\n";
    }

    return 0;
}

逻辑分析与参数说明:

  • map find() 方法可以快速定位键值对。
  • 如果找不到,返回 end() 迭代器。
  • 使用 it->second 获取对应的学生对象。

表格:vector 与 map 在学生管理系统中的比较

功能 vector 实现 map 实现
添加学生 push_back insert / []
查询学生 遍历查找(O(n)) find(O(log n))
存储方式 顺序存储 键值对排序存储
适用场景 数据量小、不频繁查询 需要高效查找的场景

通过本章的学习,你已经掌握了 STL 容器的核心概念、操作方法以及在实际项目中的应用。接下来的章节将介绍 STL 迭代器与算法的使用,进一步提升你的 C++ 编程能力。

4. STL迭代器与算法优化实践

STL(Standard Template Library)是C++标准库中最强大的组件之一,其核心包括容器(Containers)、迭代器(Iterators)和算法(Algorithms)三大组成部分。本章将深入探讨迭代器的分类与使用方式,并结合STL算法与函数对象进行优化实践,帮助开发者在实际项目中提升程序性能与代码可读性。

4.1 迭代器的类型与使用方式

迭代器是连接容器与算法之间的桥梁,它提供了统一的访问接口,使得算法可以独立于容器类型进行操作。理解不同类型的迭代器及其使用方式,是掌握STL算法优化的关键。

4.1.1 输入/输出/前向/双向/随机访问迭代器

STL定义了五种基本类型的迭代器,每种迭代器支持的操作不同,适用于不同的容器和算法需求。

迭代器类型 支持操作 示例容器
输入迭代器 读取一次,前向移动 istream_iterator
输出迭代器 写入一次,前向移动 ostream_iterator
前向迭代器 读写,前向移动 forward_list
双向迭代器 读写,双向移动 list set
随机访问迭代器 读写,随机访问,支持算术运算 vector deque

使用建议
- 若需频繁访问任意位置元素,优先使用随机访问迭代器(如 vector );
- 若仅需顺序遍历,前向或双向迭代器更节省内存;
- 输入/输出迭代器用于流式输入输出操作,常用于算法与流之间的交互。

4.1.2 迭代器与容器的绑定方式

每个容器都提供了自己的迭代器类型,通过调用 begin() end() 方法获取。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    // 获取迭代器
    std::vector<int>::iterator it = vec.begin();
    while (it != vec.end()) {
        std::cout << *it << " ";
        ++it;
    }
    return 0;
}
代码逻辑分析:
  1. vec.begin() 返回指向第一个元素的迭代器;
  2. vec.end() 返回指向“尾后”的迭代器,用于判断是否结束;
  3. 使用 *it 访问当前元素;
  4. ++it 移动到下一个元素;
  5. 循环遍历整个容器。
参数说明:
  • std::vector<int>::iterator vector 专用的迭代器类型;
  • 所有STL容器均支持 begin() end() 函数;
  • *it 为解引用操作符,获取当前元素的引用;
  • ++it 表示迭代器前移,效率高于 it++ (前缀自增)。

4.2 STL常用算法与函数对象

STL算法库提供了丰富的通用算法,如查找、排序、变换等。这些算法通常通过迭代器操作数据,与容器解耦,具有极高的复用性。

4.2.1 查找、排序与变换算法

以下是一些常用的STL算法示例:

#include <iostream>
#include <vector>
#include <algorithm> // 包含常用算法

int main() {
    std::vector<int> vec = {5, 3, 1, 4, 2};

    // 排序
    std::sort(vec.begin(), vec.end());

    // 查找
    auto it = std::find(vec.begin(), vec.end(), 3);
    if (it != vec.end()) {
        std::cout << "找到元素3,索引为: " << std::distance(vec.begin(), it) << std::endl;
    }

    // 变换:将每个元素乘以2
    std::transform(vec.begin(), vec.end(), vec.begin(), [](int x) { return x * 2; });

    // 输出变换后的结果
    for (int x : vec) {
        std::cout << x << " ";
    }

    return 0;
}
代码逻辑分析:
  1. std::sort(vec.begin(), vec.end())
    - 将 vector 中的元素按升序排序;
    - 时间复杂度为 O(n log n),适用于随机访问迭代器。

  2. std::find(vec.begin(), vec.end(), 3)
    - 从 begin() end() 查找值为3的元素;
    - 返回匹配的迭代器,若未找到则返回 end()

  3. std::transform(vec.begin(), vec.end(), vec.begin(), lambda)
    - 对 vec 中的每个元素应用一个函数;
    - 第三个参数为输出迭代器,这里复用原容器;
    - 使用lambda表达式实现元素乘以2的操作。

  4. std::distance(vec.begin(), it)
    - 计算两个迭代器之间的距离,适用于双向或随机访问迭代器。

参数说明:
  • std::sort 要求容器支持随机访问迭代器;
  • std::find 可用于任何迭代器类型;
  • std::transform 可用于所有容器,但需注意输出容器的大小是否匹配。

4.2.2 函数对象与lambda表达式

STL算法支持使用函数对象(Function Objects)或Lambda表达式作为参数,实现灵活的逻辑封装。

#include <iostream>
#include <vector>
#include <algorithm>

struct IsEven {
    bool operator()(int x) const {
        return x % 2 == 0;
    }
};

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6};

    // 使用函数对象
    int count = std::count_if(vec.begin(), vec.end(), IsEven());
    std::cout << "偶数个数:" << count << std::endl;

    // 使用lambda表达式
    count = std::count_if(vec.begin(), vec.end(), [](int x) { return x > 3; });
    std::cout << "大于3的元素个数:" << count << std::endl;

    return 0;
}
代码逻辑分析:
  1. IsEven 结构体
    - 定义了一个函数对象,重载了 operator()
    - 可作为谓词传入 std::count_if

  2. std::count_if
    - 统计满足条件的元素个数;
    - 第三个参数可以是函数对象、lambda表达式或普通函数指针。

  3. Lambda表达式 [](int x) { return x > 3; }
    - 捕获列表为空,表示不捕获外部变量;
    - 接收一个 int 参数,返回布尔值;
    - 在 count_if 中使用非常方便。

参数说明:
  • 函数对象适合复用,Lambda适合临时逻辑;
  • Lambda表达式可简化代码结构,提高可读性;
  • 函数对象支持状态保存,而Lambda默认为无状态。

4.3 算法性能优化技巧

STL算法虽然高效,但若使用不当,也可能导致性能瓶颈。掌握以下优化技巧,可以显著提升程序执行效率。

4.3.1 选择合适的算法与容器组合

不同算法对容器的访问模式不同,选择不当可能导致性能下降。

容器类型 随机访问 插入/删除 推荐算法
vector sort , find , transform
list splice , remove , merge
deque push_back , pop_front
示例优化:
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
#include <chrono>

int main() {
    std::vector<int> vec(1000000);
    std::list<int> lst(1000000);

    auto start = std::chrono::high_resolution_clock::now();
    std::sort(vec.begin(), vec.end());
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "vector排序耗时:" 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() 
              << " ms" << std::endl;

    start = std::chrono::high_resolution_clock::now();
    lst.sort();
    end = std::chrono::high_resolution_clock::now();
    std::cout << "list排序耗时:" 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() 
              << " ms" << std::endl;

    return 0;
}
说明:
  • vector 支持随机访问,排序效率高;
  • list 只能使用成员函数 sort() ,效率较低;
  • 对于大数据量排序,优先选择 vector

4.3.2 减少不必要的对象拷贝

避免在算法中频繁拷贝对象,可使用引用或指针来优化。

#include <iostream>
#include <vector>
#include <algorithm>

struct Data {
    int id;
    std::string name;
};

int main() {
    std::vector<Data> dataVec;
    for (int i = 0; i < 100000; ++i) {
        dataVec.push_back({i, "name"});
    }

    // 使用引用避免拷贝
    std::for_each(dataVec.begin(), dataVec.end(), [](const Data& d) {
        std::cout << d.id << " ";
    });

    return 0;
}
优化说明:
  • 使用 const Data& 引用传递,避免对象拷贝;
  • 对于大型结构体,拷贝代价昂贵;
  • 使用引用或指针可显著提升性能。

4.4 实战优化案例:高效查找与排序日志数据

假设我们有一个日志系统,需要处理大量日志记录,包括查找特定时间范围内的日志和按时间排序显示。

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <ctime>

struct LogEntry {
    time_t timestamp;
    std::string message;
};

bool compareLogs(const LogEntry& a, const LogEntry& b) {
    return a.timestamp < b.timestamp;
}

int main() {
    std::vector<LogEntry> logs = {
        {std::time(nullptr) - 3600, "Error: Disk full"},
        {std::time(nullptr) - 7200, "Warning: Low memory"},
        {std::time(nullptr), "Info: System started"}
    };

    // 按时间排序
    std::sort(logs.begin(), logs.end(), compareLogs);

    // 查找特定时间段的日志
    time_t now = std::time(nullptr);
    auto it = std::partition_point(logs.begin(), logs.end(), [now](const LogEntry& entry) {
        return entry.timestamp < now - 3600;
    });

    std::cout << "过去一小时内的日志:" << std::endl;
    for (auto it2 = it; it2 != logs.end(); ++it2) {
        std::cout << ctime(&it2->timestamp) << " - " << it2->message << std::endl;
    }

    return 0;
}
流程图(mermaid):
graph TD
    A[初始化日志数据] --> B[按时间排序]
    B --> C[设置时间阈值]
    C --> D[查找符合条件的日志]
    D --> E[输出筛选结果]
优化点总结:
  1. 排序优化 :使用 std::sort 结合自定义比较函数,提升排序效率;
  2. 查找优化 :使用 std::partition_point 进行二分查找,比线性查找快;
  3. 时间比较 :通过 timestamp 直接比较,避免字符串解析;
  4. 内存管理 :使用 vector 存储日志,避免频繁动态分配。
实际应用场景:
  • 日志分析系统;
  • 数据聚合与展示;
  • 实时监控与告警。

本章通过深入讲解STL迭代器类型、算法使用及优化技巧,并结合日志系统实战案例,帮助开发者掌握如何在C++项目中高效使用STL进行数据处理与性能优化。

5. Visual C++数据库编程(ODBC/ADO)

数据库编程是现代软件开发中不可或缺的一环,尤其在企业级应用开发中,数据持久化与高效管理显得尤为重要。在Visual C++中,开发者可以通过ODBC(Open Database Connectivity)和ADO(ActiveX Data Objects)两种主要方式实现数据库访问。本章将从数据库连接机制入手,深入讲解ODBC与ADO的使用方式,结合具体操作步骤与代码示例,帮助开发者掌握如何在C++中进行数据库编程,并通过一个完整的库存管理系统案例,展示数据库编程的实战应用。

5.1 ODBC与ADO数据库访问机制

ODBC和ADO是Windows平台上常用的数据库访问接口,它们分别适用于不同的开发场景和需求。

5.1.1 ODBC连接与驱动配置

ODBC是一个标准的数据库访问接口,它允许应用程序通过统一的方式访问多种数据库系统。ODBC的核心是驱动管理器和数据库驱动程序。

ODBC连接步骤:
  1. 配置ODBC数据源
    - 打开“ODBC数据源管理器”(控制面板 -> 管理工具 -> 数据源(ODBC))。
    - 在“用户DSN”或“系统DSN”中添加新的数据源。
    - 选择数据库类型(如SQL Server、Access等),并配置服务器地址、数据库名称、登录凭据等信息。

  2. C++中使用ODBC连接数据库
    使用ODBC API函数进行数据库连接和操作。以下是一个简单的连接示例:

#include <windows.h>
#include <sql.h>
#include <sqlext.h>

int main() {
    SQLHENV hEnv = NULL;
    SQLHDBC hDbc = NULL;
    SQLRETURN retCode;

    // 分配环境句柄
    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
    // 设置ODBC版本
    SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
    // 分配连接句柄
    SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);

    // 连接数据源(假设DSN为"MyAccessDB")
    retCode = SQLConnect(hDbc, (SQLCHAR*)"MyAccessDB", SQL_NTS,
                         (SQLCHAR*)"username", SQL_NTS,
                         (SQLCHAR*)"password", SQL_NTS);

    if (retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO) {
        printf("连接成功!\n");
        // 在此执行SQL操作...
        SQLDisconnect(hDbc);
    } else {
        printf("连接失败!\n");
    }

    // 释放资源
    SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
    SQLFreeHandle(SQL_HANDLE_ENV, hEnv);

    return 0;
}
代码逻辑分析:
  • SQLAllocHandle :分配环境和连接句柄。
  • SQLSetEnvAttr :设置ODBC版本为3.x,以支持更高级的功能。
  • SQLConnect :通过DSN、用户名和密码建立连接。
  • SQLDisconnect :断开数据库连接。
  • SQLFreeHandle :释放句柄资源,避免内存泄漏。
参数说明:
函数 参数说明
SQLConnect 第一个参数为数据源名称(DSN),后两个分别为用户名和密码
SQLAllocHandle 第一个参数为句柄类型(SQL_HANDLE_ENV或SQL_HANDLE_DBC)

5.1.2 ADO接口与COM组件调用

ADO(ActiveX Data Objects)是基于COM(Component Object Model)的数据库访问接口,使用简单、功能强大,适合快速开发。

ADO连接数据库的步骤:
  1. 初始化COM库
    ADO基于COM,必须先调用 CoInitialize 初始化COM环境。

  2. 创建Connection对象
    使用 _ConnectionPtr 接口连接数据库。

  3. 执行SQL语句
    使用 _CommandPtr 执行查询或操作语句。

  4. 释放资源
    关闭连接并调用 CoUninitialize 释放COM资源。

示例代码:
#include <comdef.h>
#include <ado.h>

#pragma comment(lib, "uuid.lib")

int main() {
    CoInitialize(NULL);

    _ConnectionPtr pConn;
    HRESULT hr = pConn.CreateInstance(__uuidof(Connection));
    if (SUCCEEDED(hr)) {
        try {
            pConn->Open("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=mydb.accdb;", "", "", adConnectUnspecified);
            printf("ADO连接成功!\n");

            // 执行SQL语句示例
            _CommandPtr pCmd;
            pCmd.CreateInstance(__uuidof(Command));
            pCmd->ActiveConnection = pConn;
            pCmd->CommandText = "SELECT * FROM Users";
            _RecordsetPtr pRs = pCmd->Execute(NULL, NULL, adCmdText);

            while (!pRs->adoEOF) {
                _variant_t name = pRs->GetCollect("Name");
                printf("Name: %s\n", (char*)_bstr_t(name));
                pRs->MoveNext();
            }

            pRs->Close();
        }
        catch (_com_error &e) {
            printf("Error: %s\n", e.ErrorMessage());
        }
        pConn->Close();
    }

    CoUninitialize();
    return 0;
}
代码逻辑分析:
  • CoInitialize(NULL) :初始化COM环境。
  • pConn.CreateInstance :创建数据库连接对象。
  • pConn->Open :使用连接字符串连接到Access数据库。
  • pCmd->Execute :执行SQL查询并返回记录集。
  • pRs->GetCollect :获取字段值。
参数说明:
参数 说明
ConnectionString 数据源提供者(如ACE OLEDB)、数据库路径等信息
CommandText 要执行的SQL语句

5.2 数据库连接与操作实践

5.2.1 使用C++连接Access/SQL Server

ODBC和ADO均可用于连接Access和SQL Server数据库,以下表格总结了两者的连接字符串格式:

数据库类型 ODBC连接字符串示例 ADO连接字符串示例
Access DSN=MyAccessDB;UID=;PWD=; Provider=Microsoft.ACE.OLEDB.12.0;Data Source=mydb.accdb;
SQL Server DSN=MySqlServer;UID=sa;PWD=123456; Provider=SQLOLEDB;Data Source=.;Initial Catalog=mydb;User ID=sa;Password=123456;
示例:ODBC连接SQL Server
retCode = SQLConnect(hDbc, (SQLCHAR*)"MySqlServer", SQL_NTS,
                     (SQLCHAR*)"sa", SQL_NTS,
                     (SQLCHAR*)"123456", SQL_NTS);

5.2.2 数据库增删改查操作实现

插入操作(INSERT)
SQLExecDirect(hStmt, (SQLCHAR*)"INSERT INTO Users (Name, Age) VALUES ('Tom', 25)", SQL_NTS);
查询操作(SELECT)
SQLExecDirect(hStmt, (SQLCHAR*)"SELECT * FROM Users", SQL_NTS);
SQLBindCol(hStmt, 1, SQL_C_LONG, &id, 0, NULL);
SQLBindCol(hStmt, 2, SQL_C_CHAR, name, 255, NULL);
while (SQLFetch(hStmt) != SQL_NO_DATA) {
    printf("ID: %d, Name: %s\n", id, name);
}
更新操作(UPDATE)
SQLExecDirect(hStmt, (SQLCHAR*)"UPDATE Users SET Age=30 WHERE Name='Tom'", SQL_NTS);
删除操作(DELETE)
SQLExecDirect(hStmt, (SQLCHAR*)"DELETE FROM Users WHERE Name='Tom'", SQL_NTS);

5.3 数据绑定与界面交互

5.3.1 控件与数据库字段绑定

在MFC(Microsoft Foundation Classes)框架中,可以将数据库字段与控件进行绑定,实现数据的自动填充与更新。

使用ClassWizard绑定字段:
  1. 在资源视图中添加数据库控件(如CRecordView)。
  2. 使用ClassWizard将控件与记录集字段绑定。
  3. 重写 DoDataExchange 函数实现数据同步。
示例代码片段:
void CMyView::DoDataExchange(CDataExchange* pDX)
{
    CRecordView::DoDataExchange(pDX);
    DDX_FieldText(pDX, IDC_NAME, &m_pSet->m_Name, m_pSet);
    DDX_FieldText(pDX, IDC_AGE, &m_pSet->m_Age, m_pSet);
}

5.3.2 界面中显示和更新数据

使用ADO实现数据绑定(ADO + MFC)
_RecordsetPtr pRs = pConn->Execute("SELECT * FROM Users", NULL, adCmdText);
while (!pRs->adoEOF) {
    CString name = (char*)_bstr_t(pRs->Fields->GetItem("Name")->Value);
    int age = pRs->Fields->GetItem("Age")->Value.lVal;
    m_listCtrl.InsertItem(0, name);
    m_listCtrl.SetItemText(0, 1, CString::Format(_T("%d"), age));
    pRs->MoveNext();
}

5.4 数据库应用实例:简易库存管理系统

5.4.1 用户界面设计与数据库结构

数据库设计(SQL Server)
CREATE TABLE Products (
    ProductID INT PRIMARY KEY IDENTITY(1,1),
    ProductName NVARCHAR(100),
    Quantity INT,
    Price DECIMAL(10,2)
);
界面设计(MFC)
  • 添加按钮:新增、编辑、删除、查询
  • 列表控件:显示产品信息

5.4.2 核心功能模块实现

新增产品
CString sql;
sql.Format(_T("INSERT INTO Products (ProductName, Quantity, Price) VALUES ('%s', %d, %.2f)"),
           m_strName, m_nQty, m_fPrice);
pConn->Execute((_bstr_t)sql, NULL, adCmdText);
查询产品
CString sql;
sql.Format(_T("SELECT * FROM Products WHERE ProductName LIKE '%%%s%%'"), m_strSearch);
_RecordsetPtr pRs = pConn->Execute((_bstr_t)sql, NULL, adCmdText);
// 遍历记录并显示到列表控件中...
删除产品
sql.Format(_T("DELETE FROM Products WHERE ProductID = %d"), m_nSelectedID);
pConn->Execute((_bstr_t)sql, NULL, adCmdText);

小结

本章深入讲解了Visual C++中使用ODBC和ADO进行数据库编程的核心技术。通过代码示例和实际案例,我们掌握了如何配置数据源、连接数据库、执行增删改查操作,并实现了数据绑定与界面交互。最后,通过一个简易库存管理系统的完整实现,展示了数据库编程在实际项目中的应用价值。下一章将进入Windows API开发,继续深入探讨图形界面编程与事件处理机制。

6. Windows API开发:窗口创建与消息处理

Windows API(Application Programming Interface)是Windows操作系统提供的一组函数接口,允许开发者直接与操作系统进行交互。本章将介绍如何使用C++结合Windows API开发图形界面程序,包括窗口创建、控件管理、消息处理机制以及事件响应等内容。通过本章学习,读者将掌握基于Win32 API开发Windows桌面应用程序的核心技能。

6.1 Windows程序结构与API基础

Windows应用程序与控制台程序在结构上存在显著差异,其入口点不是 main 函数,而是 WinMain 函数。此外,Windows程序通过注册窗口类、创建窗口、进入消息循环等步骤实现图形界面交互。

6.1.1 WinMain函数与程序入口

WinMain 是Windows应用程序的入口函数,其函数原型如下:

int WINAPI WinMain(
    HINSTANCE hInstance,        // 当前实例句柄
    HINSTANCE hPrevInstance,    // 前一个实例句柄(已弃用)
    LPSTR lpCmdLine,            // 命令行参数
    int nCmdShow                // 窗口显示方式
);

以下是一个最简单的Windows程序框架:

#include <windows.h>

// 窗口过程函数声明
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // 1. 定义并注册窗口类
    WNDCLASS wc = {};
    wc.lpfnWndProc   = WindowProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = L"SampleWindowClass";

    RegisterClass(&wc);

    // 2. 创建窗口
    HWND hwnd = CreateWindowEx(
        0,                              // 扩展样式
        L"SampleWindowClass",           // 窗口类名
        L"Hello Windows API",           // 窗口标题
        WS_OVERLAPPEDWINDOW,            // 窗口样式
        CW_USEDEFAULT, CW_USEDEFAULT,   // 初始位置
        800, 600,                       // 窗口大小
        NULL,                           // 父窗口句柄
        NULL,                           // 菜单句柄
        hInstance,                      // 应用实例句柄
        NULL                            // 附加参数
    );

    if (hwnd == NULL) {
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);

    // 3. 消息循环
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

6.1.2 窗口类与窗口过程函数

  • WNDCLASS 结构体用于注册窗口类,其中 lpfnWndProc 指向窗口过程函数。
  • 窗口过程函数是处理所有窗口消息的核心函数,其定义如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

例如,实现一个关闭窗口的处理:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            TextOut(hdc, 50, 50, L"Hello, Windows API!", 21);
            EndPaint(hwnd, &ps);
            return 0;
        }
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

6.2 窗口创建与控件管理

Windows API允许开发者通过控件(如按钮、编辑框等)来丰富用户界面交互。控件本质上是子窗口,通过特定的窗口类名和样式创建。

6.2.1 创建基本窗口与设置样式

CreateWindowEx 函数中,可通过设置 dwStyle 参数来控制窗口外观,例如:

  • WS_OVERLAPPEDWINDOW :标准窗口样式
  • WS_POPUP :无边框窗口
  • WS_CAPTION :带标题栏
HWND hwnd = CreateWindowEx(
    WS_EX_CLIENTEDGE,                  // 扩展样式
    L"SampleWindowClass",              // 窗口类名
    L"Styled Window",                  // 标题
    WS_OVERLAPPEDWINDOW | WS_VSCROLL,  // 主样式 + 垂直滚动条
    CW_USEDEFAULT, CW_USEDEFAULT,
    800, 600,
    NULL, NULL, hInstance, NULL);

6.2.2 添加按钮、编辑框等控件

使用 CreateWindow 函数创建控件,例如创建按钮:

HWND hwndButton = CreateWindow(
    L"BUTTON",                      // 预定义控件类名
    L"Click Me",                    // 按钮文本
    WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,  // 样式
    50, 100, 100, 30,               // 位置与大小
    hwnd,                           // 父窗口句柄
    (HMENU)101,                     // 控件ID
    hInstance,                      // 实例句柄
    NULL);                          // 附加参数

在窗口过程函数中响应按钮点击:

case WM_COMMAND:
    if (LOWORD(wParam) == 101) {  // 控件ID为101
        MessageBox(hwnd, L"Button clicked!", L"Info", MB_OK);
    }
    break;

6.3 消息处理机制与事件响应

Windows采用消息驱动机制,所有用户交互(如点击、输入、移动窗口)都转化为消息发送给窗口过程函数处理。

6.3.1 消息循环与处理流程

消息循环结构如下:

MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
  • GetMessage :从消息队列中获取消息
  • TranslateMessage :将键盘消息转换为字符消息
  • DispatchMessage :将消息分发给对应的窗口过程函数

下图展示了Windows消息处理流程:

graph TD
    A[应用程序启动] --> B[注册窗口类]
    B --> C[创建窗口]
    C --> D[显示窗口]
    D --> E[进入消息循环]
    E --> F{是否有消息?}
    F -- 是 --> G[TranslateMessage]
    G --> H[DispatchMessage]
    H --> I[调用WindowProc]
    I --> J[处理消息]
    J --> E
    F -- 否 --> K[退出程序]

6.3.2 常见消息处理函数实现

常见消息包括:

  • WM_CREATE :窗口创建时触发
  • WM_SIZE :窗口大小改变
  • WM_MOUSEMOVE :鼠标移动
  • WM_KEYDOWN :按键按下

示例:处理窗口大小变化:

case WM_SIZE:
    int width = LOWORD(lParam);
    int height = HIWORD(lParam);
    // 更新控件布局
    MoveWindow(hwndButton, 10, 10, width - 20, 30, TRUE);
    break;

6.4 实战项目:自定义绘图窗口与事件交互

6.4.1 实现鼠标绘图功能

WM_MOUSEMOVE WM_LBUTTONDOWN 消息中记录鼠标位置,并在 WM_PAINT 中绘制线条:

POINT ptStart, ptEnd;

case WM_LBUTTONDOWN:
    ptStart.x = LOWORD(lParam);
    ptStart.y = HIWORD(lParam);
    break;

case WM_MOUSEMOVE:
    if (wParam & MK_LBUTTON) {
        ptEnd.x = LOWORD(lParam);
        ptEnd.y = HIWORD(lParam);
        InvalidateRect(hwnd, NULL, FALSE);  // 强制重绘
    }
    break;

case WM_PAINT: {
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);
    MoveToEx(hdc, ptStart.x, ptStart.y, NULL);
    LineTo(hdc, ptEnd.x, ptEnd.y);
    EndPaint(hwnd, &ps);
    break;
}

6.4.2 键盘输入与窗口响应

WM_KEYDOWN 中处理键盘事件:

case WM_KEYDOWN:
    switch (wParam) {
        case VK_LEFT:
            // 向左移动
            break;
        case VK_RIGHT:
            // 向右移动
            break;
    }
    break;

下一章节将继续深入探讨Windows GDI绘图与多线程编程等内容,敬请期待。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《Visual C++实战编程100例(第2版)》是一本专注于Visual C++编程实践的技术书籍,结合完整源代码帮助读者掌握C++在Windows平台下的开发技巧。内容涵盖C++基础语法、面向对象编程、标准模板库(STL)应用、数据库连接(ODBC/ADO)、Windows API使用、设计模式实践、调试优化技巧等多个方面。通过100个实例项目,读者可系统学习Visual C++在真实开发场景中的应用,提升代码质量与项目实战能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值