简介:《Visual C++实战编程100例(第2版)》是一本专注于Visual C++编程实践的技术书籍,结合完整源代码帮助读者掌握C++在Windows平台下的开发技巧。内容涵盖C++基础语法、面向对象编程、标准模板库(STL)应用、数据库连接(ODBC/ADO)、Windows API使用、设计模式实践、调试优化技巧等多个方面。通过100个实例项目,读者可系统学习Visual C++在真实开发场景中的应用,提升代码质量与项目实战能力。
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;
}
代码逻辑分析:
-
vec.begin()返回指向第一个元素的迭代器; -
vec.end()返回指向“尾后”的迭代器,用于判断是否结束; - 使用
*it访问当前元素; -
++it移动到下一个元素; - 循环遍历整个容器。
参数说明:
-
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;
}
代码逻辑分析:
-
std::sort(vec.begin(), vec.end())
- 将vector中的元素按升序排序;
- 时间复杂度为 O(n log n),适用于随机访问迭代器。 -
std::find(vec.begin(), vec.end(), 3)
- 从begin()到end()查找值为3的元素;
- 返回匹配的迭代器,若未找到则返回end()。 -
std::transform(vec.begin(), vec.end(), vec.begin(), lambda)
- 对vec中的每个元素应用一个函数;
- 第三个参数为输出迭代器,这里复用原容器;
- 使用lambda表达式实现元素乘以2的操作。 -
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;
}
代码逻辑分析:
-
IsEven结构体
- 定义了一个函数对象,重载了operator();
- 可作为谓词传入std::count_if。 -
std::count_if
- 统计满足条件的元素个数;
- 第三个参数可以是函数对象、lambda表达式或普通函数指针。 -
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[输出筛选结果]
优化点总结:
- 排序优化 :使用
std::sort结合自定义比较函数,提升排序效率; - 查找优化 :使用
std::partition_point进行二分查找,比线性查找快; - 时间比较 :通过
timestamp直接比较,避免字符串解析; - 内存管理 :使用
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连接步骤:
-
配置ODBC数据源
- 打开“ODBC数据源管理器”(控制面板 -> 管理工具 -> 数据源(ODBC))。
- 在“用户DSN”或“系统DSN”中添加新的数据源。
- 选择数据库类型(如SQL Server、Access等),并配置服务器地址、数据库名称、登录凭据等信息。 -
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连接数据库的步骤:
-
初始化COM库
ADO基于COM,必须先调用CoInitialize初始化COM环境。 -
创建Connection对象
使用_ConnectionPtr接口连接数据库。 -
执行SQL语句
使用_CommandPtr执行查询或操作语句。 -
释放资源
关闭连接并调用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绑定字段:
- 在资源视图中添加数据库控件(如CRecordView)。
- 使用ClassWizard将控件与记录集字段绑定。
- 重写
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绘图与多线程编程等内容,敬请期待。
简介:《Visual C++实战编程100例(第2版)》是一本专注于Visual C++编程实践的技术书籍,结合完整源代码帮助读者掌握C++在Windows平台下的开发技巧。内容涵盖C++基础语法、面向对象编程、标准模板库(STL)应用、数据库连接(ODBC/ADO)、Windows API使用、设计模式实践、调试优化技巧等多个方面。通过100个实例项目,读者可系统学习Visual C++在真实开发场景中的应用,提升代码质量与项目实战能力。
388

被折叠的 条评论
为什么被折叠?



