C++入门指南:从C语言基础到面向对象编程实战

发布时间:2026/7/26 7:27:47

C++入门指南:从C语言基础到面向对象编程实战 1. 从C到C跨越门槛拥抱对象如果你已经啃完了C语言恭喜你你已经拿到了进入系统级编程世界的第一把钥匙。C语言教会了你如何与内存、指针、函数这些底层概念打交道让你理解了程序在机器层面是如何运作的。现在是时候拿起第二把钥匙打开C这扇更广阔的大门了。很多人说C是“带类的C”这话对但也不全对。它确实在C的基础上增加了面向对象、泛型编程等强大的特性但它的设计哲学远不止是简单的加法。学习C更像是在你已经熟悉的C语言地基上学习如何建造更复杂、更模块化、也更安全的大型建筑。为什么需要C语言基础因为C几乎完全兼容C的语法在C中称为“C子集”你的指针、结构体、内存管理知识是理解C许多高级特性的基石。例如C的引用底层可以看作是指针的语法糖类的成员函数调用背后隐藏着this指针没有C语言的基础这些概念会变得非常抽象和难以理解。学习C不是抛弃C而是站在C的肩膀上用更高级的抽象来管理C所暴露的复杂性。你的目标不再是仅仅让程序“跑起来”而是要让程序结构清晰、易于维护、可扩展性强。接下来我会带你从环境搭建开始一步步拆解C的核心特性并分享那些只有踩过坑才知道的实操细节。2. 工欲善其事开发环境搭建与第一个程序在开始写代码之前一个顺手的开发环境至关重要。对于C新手尤其是从C语言过渡过来的我强烈推荐两个选择Visual Studio Community和VSCode 插件。前者是“全家桶”开箱即用后者是“定制工具箱”轻量灵活。2.1 环境方案选型VS与VSCode的抉择Visual Studio Community是微软官方的免费IDE对于Windows平台下的C开发极其友好。它的优势在于集成度极高安装时勾选“使用C的桌面开发”编译器MSVC、调试器、项目管理系统、图形界面设计器全都一次性搞定。你完全不用操心如何链接库、如何配置编译参数特别适合快速上手和开发带有图形界面的应用程序比如用MFC或Windows Forms。它的调试器是我用过最直观强大的之一查看变量、调用堆栈、内存都非常方便。VSCode MinGW-w64则是另一种流派。VSCode本身是一个轻量级编辑器通过安装C/C插件Microsoft官方发布、Code Runner等插件可以变身成一个强大的开发环境。你需要自己安装编译器通常选择MinGW-w64它提供了GCC编译器在Windows上的移植版。这套方案的优点在于轻快、可定制性强而且跨平台配置方法在Linux和macOS上类似。你可以更清晰地了解编译、链接的每一个步骤对于理解构建过程很有帮助。如果你未来可能接触Linux开发或者喜欢折腾这个方案更合适。注意网络上很多教程提到的“Microsoft Visual C Redistributable”这是运行时库是运行别人编译好的C程序所需要的不是开发环境。开发需要的是编译器如MSVC, GCC和SDK。我个人建议如果你是纯粹的Windows初学者想避开环境配置的坑直接上手写代码选Visual Studio Community。如果你想更深入地理解工具链或者有跨平台需求那就花点时间配置VSCode。下面我以VSCode配置为例因为其中涉及的原理更具普适性。2.2 VSCode环境配置步步为营假设你已安装好VSCode接下来是详细的配置流程。这个过程看似繁琐但理解后一通百通。安装编译器MinGW-w64去 SourceForge 或 WinLibs 下载MinGW-w64的离线安装包或压缩包。我推荐下载x86_64-posix-seh版本的压缩包。解压到一个没有中文和空格的路径比如D:\Dev\mingw64。将编译器的bin目录例如D:\Dev\mingw64\bin添加到系统的环境变量Path中。这是最关键的一步否则命令行找不到g命令。安装VSCode插件在VSCode扩展商店搜索并安装C/C(由Microsoft发布)。这个插件提供代码智能感知IntelliSense、调试、浏览等功能。安装Code Runner。这个插件可以让你一键运行多种语言的代码片段非常方便。配置项目与编译器路径在你喜欢的位置创建一个文件夹作为工作区例如Cpp_Projects。用VSCode打开这个文件夹。在该文件夹下新建一个HelloWorld.cpp文件。在VSCode中按CtrlShiftP输入C/C: Edit Configurations (UI)回车。这会打开一个图形化配置界面。在编译器路径一项点击下拉箭头VSCode通常会自动检测到你添加到Path的GCC。如果没有就手动浏览到mingw64\bin目录下的g.exe。IntelliSense 模式选择gcc-x64。这些配置会保存在工作区下的.vscode/c_cpp_properties.json文件中。这个文件的作用是告诉C/C插件如何分析你的代码。配置构建任务可选但推荐按CtrlShiftP输入Tasks: Configure Task再选择Create tasks.json file from template然后选Others。这会创建一个运行外部命令的模板。将tasks.json的内容替换为以下内容这定义了一个使用g编译当前活动文件的构建任务{ version: 2.0.0, tasks: [ { label: build with g, type: shell, command: g, args: [ -g, ${file}, -o, ${fileDirname}\\${fileBasenameNoExtension}.exe ], group: { kind: build, isDefault: true }, presentation: { reveal: always }, problemMatcher: $gcc } ] }这样你可以按CtrlShiftB直接编译当前文件生成同名的exe。编写并运行第一个程序在HelloWorld.cpp中输入经典的代码#include iostream using namespace std; int main() { cout Hello, C World! endl; return 0; }你可以右键选择Run CodeCode Runner插件或者按CtrlShiftB构建后再在终端运行.\HelloWorld.exe。实操心得环境配置是新手的第一道坎90%的问题出在环境变量Path没有设对或者路径中有中文空格。一个检验方法是打开一个新的终端cmd或PowerShell输入g --version如果能正确输出版本信息就说明编译器配置成功了。如果失败请仔细检查Path和环境变量是否生效可能需要重启终端或电脑。3. C核心特性深度解析不止于“类”有了环境我们来深入看看C在C的基础上到底加了哪些“猛料”。很多人学C只学了个“类”这是远远不够的。3.1 引用更安全的“别名”指针引用是C引入的一个关键特性。你可以把它理解为某个已存在变量的“别名”。它必须在定义时初始化并且一旦绑定到一个变量就不能再指向其他变量。int a 10; int ref a; // ref是a的引用ref就是a的另一个名字 ref 20; // 修改ref等同于修改a cout a; // 输出20为什么需要引用函数参数传递在C中如果你想在函数内修改外部变量必须传指针。这导致函数调用时到处都是取地址和*解引用代码可读性差。在C中你可以使用引用参数函数内操作的就是实参本身语法更简洁安全。// C风格 void swap_c(int *a, int *b) { int t *a; *a *b; *b t; } swap_c(x, y); // C风格 void swap_cpp(int a, int b) { int t a; a b; b t; } swap_cpp(x, y); // 看起来就像传值但实际修改了x和y避免拷贝开销对于大型结构体或类对象按值传递会触发拷贝构造函数产生性能开销。传递const引用是常见的做法既能避免拷贝又能防止函数内部误修改。void printBigObject(const VeryLargeObject obj) { // 读取obj但不会拷贝它也不能修改它 }注意事项引用本身不是对象它没有独立的内存地址虽然编译器可能用指针实现它。sizeof(引用)得到的是被引用对象的大小。不能定义指向引用的指针也不能建立引用的数组。3.2 函数重载与默认参数提升接口灵活性C语言不允许同名函数存在即使参数类型不同。C的函数重载允许在同一作用域内定义多个同名函数只要它们的参数列表参数类型、数量、顺序不同即可。编译器会根据调用时提供的实参来决定调用哪个函数。void print(int i) { cout Integer: i endl; } void print(double f) { cout Float: f endl; } void print(const char* s) { cout String: s endl; } print(10); // 调用 print(int) print(3.14); // 调用 print(double) print(hello); // 调用 print(const char*)默认参数则允许在函数声明时为参数指定一个默认值。调用时如果省略该参数就使用默认值。这可以让函数调用更简洁。默认参数必须从右向左连续设置。void setupWindow(int width, int height, const char* title My App, bool fullscreen false) { // ... } setupWindow(800, 600); // 使用默认标题和窗口模式 setupWindow(1024, 768, Game, true); // 提供所有参数背后的原理C编译器通过“名称修饰”或“名字改编”来实现重载。它会把函数名和参数类型信息编码成一个唯一的内部名称。所以对于链接器来说print(int)和print(double)是完全不同的符号。这也是为什么C代码调用C库函数时需要extern C声明就是为了禁止名称修饰保持C语言的链接规范。3.3 内存管理new/delete 与 malloc/free 的哲学之辩C语言用malloc和freeC引入了new和delete。这不仅仅是语法糖它们有本质区别。特性malloc/free(C)new/delete(C)本质库函数运算符关键字返回类型void*需要强制转换精确类型的指针无需转换内存大小需手动计算字节数编译器根据类型自动计算初始化只分配内存不初始化对象分配内存并调用构造函数初始化对象释放只释放内存先调用析构函数再释放内存失败处理返回NULL抛出std::bad_alloc异常或返回nullptr如果使用nothrow版重载不可重载可重载类级别的operator new/delete// C风格 int *p1 (int*)malloc(10 * sizeof(int)); if(p1 NULL) { /* 处理错误 */ } free(p1); // C风格 int *p2 new int[10]; // 分配10个int但未初始化POD类型 delete[] p2; // C风格带初始化 int *p3 new int(42); // 分配一个int并初始化为42 delete p3; // 对于类对象差异巨大 class MyClass { public: MyClass() { cout Constructor called. endl; } ~MyClass() { cout Destructor called. endl; } }; MyClass *obj_c (MyClass*)malloc(sizeof(MyClass)); // 只分配内存构造函数不会被调用 free(obj_c); // 只释放内存析构函数不会被调用 MyClass *obj_cpp new MyClass; // 分配内存并调用构造函数 delete obj_cpp; // 调用析构函数并释放内存核心建议在C中对于自定义类类型永远使用new/delete。混用会导致构造函数/析构函数不被调用引发资源泄漏如文件句柄、内存锁未释放。对于简单的内置类型数组虽然new/delete也可以但在现代C中更推荐使用std::vector等容器来完全避免手动内存管理。3.4 面向对象核心类与对象、三大特性这是C区别于C最显著的部分。类是创建对象的蓝图它封装了数据成员变量和操作这些数据的方法成员函数。class Rectangle { private: // 私有成员外部无法直接访问 double width; double height; public: // 公有接口外部可访问 // 构造函数对象创建时自动调用用于初始化 Rectangle(double w, double h) : width(w), height(h) { cout Rectangle created. endl; } // 成员函数 double area() const { // const成员函数承诺不修改对象状态 return width * height; } void setWidth(double w) { width w; } // 析构函数对象销毁时自动调用用于清理资源 ~Rectangle() { cout Rectangle destroyed. endl; } }; int main() { Rectangle rect(5.0, 3.0); // 栈上创建对象自动调用构造函数 cout Area: rect.area() endl; // rect.width 10; // 错误width是private rect.setWidth(10.0); // 必须通过公有接口修改 return 0; } // rect离开作用域自动调用析构函数面向对象的三大特性在C中是如何体现的封装通过public、private、protected访问说明符将数据通常为private和操作数据的接口public绑定在一起并隐藏内部实现细节。这提高了代码的安全性和可维护性。上面的Rectangle类就是一个封装的例子。继承允许我们依据另一个类来定义一个新类新类继承了父类的特性并可以添加自己的新特性。这促进了代码的复用。class Shape { // 基类 protected: string color; public: Shape(const string c) : color(c) {} virtual void draw() const { // 虚函数为多态做准备 cout Drawing a shape with color color endl; } }; class Circle : public Shape { // 公有继承 private: double radius; public: Circle(const string c, double r) : Shape(c), radius(r) {} // 重写覆盖基类的虚函数 void draw() const override { cout Drawing a circle (radius radius , color color ) endl; } };多态允许将子类对象当作父类对象来看待并且通过父类的指针或引用来调用子类重写的方法。这依赖于虚函数机制。int main() { Shape* shapes[2]; shapes[0] new Shape(red); shapes[1] new Circle(blue, 5.0); for (int i 0; i 2; i) { shapes[i]-draw(); // 多态调用 } // 输出 // Drawing a shape with color red // Drawing a circle (radius5, colorblue) delete shapes[0]; delete shapes[1]; return 0; }虚函数原理浅析包含虚函数的类会有一个隐藏的成员——虚函数表指针vptr它指向一个虚函数表vtable。vtable中存放着该类所有虚函数的地址。当通过基类指针调用虚函数时程序会通过vptr找到vtable再根据函数在表中的偏移量找到正确的函数地址进行调用。这就是“动态绑定”或“晚期绑定”。3.5 模板泛型编程的利器模板是C支持泛型编程的基础。它允许你编写与类型无关的代码。最常见的两种是函数模板和类模板。函数模板定义一个操作该操作可以适用于多种数据类型。template typename T // T是一个占位符代表某种类型 T max(T a, T b) { return (a b) ? a : b; } // 编译器会根据调用时的实参类型实例化出具体的函数 cout max(10, 20); // 实例化出 int max(int, int) cout max(3.14, 2.71); // 实例化出 double max(double, double)类模板定义一种类其某些成员的类型或方法的参数/返回类型可以是参数化的。template typename T class Box { private: T content; public: Box(const T t) : content(t) {} T getContent() const { return content; } }; Boxint intBox(123); Boxstring stringBox(Hello Template);C标准库STL几乎完全构建在模板之上比如vectorint,liststring,mapstring, int。模板极大地提高了代码的复用性但也会导致编译时间变长和错误信息晦涩难懂。4. 标准库入门告别“重复造轮子”C的强大一半在于语言本身另一半在于其庞大的标准库。从C语言过来一定要转变“什么都自己写”的习惯学会使用标准库。4.1 字符串告别字符数组的噩梦C语言的字符串是字符数组以\0结尾操作起来需要小心翼翼strcpy,strcat,strcmp等函数容易导致缓冲区溢出。C的std::string类完美解决了这些问题。#include string using std::string; string s1 Hello; string s2 World; string s3 s1 s2; // 轻松拼接自动管理内存 cout s3 endl; // Hello World cout Length: s3.length() endl; // 获取长度 cout First char: s3[0] endl; // 支持下标访问 s3.find(World); // 查找子串 s3.substr(6, 5); // 截取子串 // 无需担心内存string对象析构时会自动释放内存。4.2 输入输出流更安全、更易扩展C语言用printf和scanf需要记忆格式符且类型不安全。C使用流stream的概念cin、cout、cerr分别对应标准输入、输出、错误输出。运算符和被重载能自动识别基本数据类型。int age; double salary; string name; cout Enter your name, age and salary: ; cin name age salary; // 连续输入 cout Hello name , you are age years old and earn $ salary endl; // 文件流 #include fstream ofstream outFile(data.txt); // 输出文件流 if (outFile.is_open()) { outFile Writing this to a file. endl; outFile.close(); } ifstream inFile(data.txt); // 输入文件流 string line; while (getline(inFile, line)) { // 安全地按行读取 cout line endl; }流库的优势在于类型安全并且可以通过重载和运算符来支持自定义类型的输入输出这是printf无法做到的。4.3 容器与算法STL的核心STLStandard Template Library是C标准库的瑰宝它提供了通用的容器、迭代器和算法。容器用于存储数据的模板类。顺序容器vector动态数组、list双向链表、deque双端队列。关联容器set/multiset集合/多重集合、map/multimap键值对/多重键值对基于红黑树实现元素自动排序。无序关联容器C11unordered_set,unordered_map基于哈希表实现查找效率通常更高。迭代器类似于指针用于遍历和访问容器中的元素是容器和算法之间的桥梁。算法大量通用的模板函数用于对容器进行操作如sort,find,copy,transform等。一个典型的使用案例#include vector #include algorithm #include iostream int main() { std::vectorint vec {5, 2, 8, 1, 9}; // 使用算法排序 std::sort(vec.begin(), vec.end()); // 使用迭代器遍历 for (std::vectorint::iterator it vec.begin(); it ! vec.end(); it) { std::cout *it ; } // C11 范围for循环更简洁 for (int num : vec) { std::cout num ; } return 0; }使用STL的哲学除非有极特殊的性能或内存需求否则优先考虑使用STL容器和算法而不是自己手写链表、排序。它们经过千锤百炼在正确性、性能和异常安全方面都有保障。5. 从理论到实践典型问题与项目拆解理解了核心概念我们通过几个典型问题和一个小项目来串联知识看看如何运用。5.1 典型问题快递费计算这是一个很好的综合练习考察基本语法、分支判断和函数封装。#include iostream using namespace std; /** * 计算快递费 * param count 件数 * param isUrgent 是否加急 * return 总费用 */ double calculateExpressFee(int count, bool isUrgent) { const double PRICE_PER_ITEM 5.0; // 每件基础费用 const double URGENT_FEE 10.0; // 加急附加费 const int DISCOUNT_THRESHOLD 10; // 折扣件数阈值 const double DISCOUNT_RATE 0.9; // 折扣率9折 double total count * PRICE_PER_ITEM; // 规则1件数10总费用打9折 if (count DISCOUNT_THRESHOLD) { total * DISCOUNT_RATE; } // 规则2如果加急附加加急费 if (isUrgent) { total URGENT_FEE; } return total; } int main() { int itemCount; char urgentChoice; bool isUrgent false; cout 请输入快递件数: ; cin itemCount; cout 是否需要加急服务(y/n): ; cin urgentChoice; if (urgentChoice y || urgentChoice Y) { isUrgent true; } double fee calculateExpressFee(itemCount, isUrgent); cout 总计快递费为: fee endl; return 0; }实操心得将计算逻辑封装成函数是一个好习惯。注意常量的使用const避免“魔法数字”直接出现在代码逻辑中提高可读性和可维护性。用户输入处理时对字符大小写的判断y/Y使程序更友好。5.2 典型问题寻找较大质数这是一个涉及基础算法质数判断和数学思维的问题。#include iostream #include cmath using namespace std; /** * 判断一个数是否为质数 * param n 待判断的数 * return true如果是质数 */ bool isPrime(int n) { if (n 1) return false; if (n 2) return true; // 2是唯一的偶质数 if (n % 2 0) return false; // 排除其他偶数 // 只需检查到 sqrt(n) 即可 int limit static_castint(sqrt(n)); for (int i 3; i limit; i 2) { // 只检查奇数因子 if (n % i 0) { return false; } } return true; } int main() { int n; cout 请输入一个正整数它是两个不同质数的乘积: ; cin n; // 寻找较小的那个质因子 int smallerPrime -1; // 同样只需遍历到 sqrt(n) int limit static_castint(sqrt(n)); for (int i 2; i limit; i) { if (n % i 0) { // i是因子 int j n / i; // 对应的另一个因子 if (isPrime(i) isPrime(j) i ! j) { // 都是质数且不同 smallerPrime (i j) ? i : j; break; } } } if (smallerPrime ! -1) { int largerPrime n / smallerPrime; cout 较大的那个质数是: largerPrime endl; } else { cout 输入不符合要求 endl; } return 0; }算法优化点质数判断优化大于2的偶数肯定不是质数直接排除。检查因子时只需检查到sqrt(n)因为如果n有一个大于其平方根的因子那么它必然对应一个小于平方根的因子。寻找因子优化也只需遍历到sqrt(n)因为因子是成对出现的。类型转换使用static_castint进行显式类型转换比C风格的(int)sqrt(n)更安全、更易识别。5.3 小型综合项目简易通讯录管理我们来设计一个简单的控制台通讯录程序综合运用类、文件I/O、STL容器。#include iostream #include fstream #include vector #include string #include algorithm // for find_if using namespace std; // 联系人结构体这里用类也可以 struct Contact { string name; string phone; string email; void display() const { cout Name: name \tPhone: phone \tEmail: email endl; } }; // 通讯录管理类 class AddressBook { private: vectorContact contacts; const string filename contacts.dat; // 数据文件 public: AddressBook() { loadFromFile(); // 构造函数中加载数据 } ~AddressBook() { saveToFile(); // 析构函数中保存数据 } // 添加联系人 void addContact(const Contact c) { contacts.push_back(c); cout Contact added successfully. endl; } // 显示所有联系人 void displayAll() const { if (contacts.empty()) { cout Address book is empty. endl; return; } cout --- All Contacts --- endl; for (const auto contact : contacts) { contact.display(); } } // 按姓名查找 void findContact(const string name) const { auto it find_if(contacts.begin(), contacts.end(), [name](const Contact c) { return c.name name; }); if (it ! contacts.end()) { cout Found: ; it-display(); } else { cout Contact not found. endl; } } // 从文件加载 void loadFromFile() { ifstream inFile(filename, ios::binary); if (!inFile) { cout No existing data file, starting fresh. endl; return; } Contact c; while (inFile.read(reinterpret_castchar*(c), sizeof(Contact))) { // 注意这里直接二进制读写string对象是危险的仅作演示。 // 实际项目应序列化每个字段如先写长度再写内容。 contacts.push_back(c); } inFile.close(); cout Data loaded from file. endl; } // 保存到文件 void saveToFile() { ofstream outFile(filename, ios::binary | ios::trunc); if (!outFile) { cerr Error: Could not save to file! endl; return; } for (const auto c : contacts) { outFile.write(reinterpret_castconst char*(c), sizeof(Contact)); } outFile.close(); cout Data saved to file. endl; } }; // 简单的菜单驱动界面 int main() { AddressBook book; int choice; do { cout \n Address Book Menu endl; cout 1. Add Contact endl; cout 2. View All Contacts endl; cout 3. Find Contact by Name endl; cout 4. Exit endl; cout Enter your choice: ; cin choice; cin.ignore(); // 清除输入缓冲区中的换行符 switch (choice) { case 1: { Contact c; cout Enter name: ; getline(cin, c.name); cout Enter phone: ; getline(cin, c.phone); cout Enter email: ; getline(cin, c.email); book.addContact(c); break; } case 2: book.displayAll(); break; case 3: { string name; cout Enter name to search: ; getline(cin, name); book.findContact(name); break; } case 4: cout Exiting... endl; break; default: cout Invalid choice! endl; } } while (choice ! 4); return 0; }项目要点与缺陷分析类的封装AddressBook类封装了数据vectorContact和操作符合面向对象思想。RAII思想在构造函数中加载数据在析构函数中保存数据确保了资源的正确获取和释放即使程序异常退出析构函数也会被调用在栈对象的情况下。STL的使用使用vector管理动态数组使用find_if算法和Lambda表达式进行查找代码简洁。重要缺陷文件I/O部分直接对string对象进行二进制读写是极其危险的string内部通常包含指向堆内存的指针直接写入指针值毫无意义读回来时指针已失效会导致未定义行为通常是程序崩溃。正确的做法应该是进行序列化对于每个字符串先写入其长度再写入字符内容。读取时先读长度再分配内存读内容。这里为了简化演示突出了这个常见陷阱。输入处理混合使用cin 和getline时要注意cin.ignore()来清除缓冲区中残留的换行符否则getline会直接读到空行。6. 进阶之路与避坑指南掌握了基础想要更进一步你需要了解一些更深入的主题和常见的“坑”。6.1 必须理解的关键进阶概念拷贝控制拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数。这五个函数管理着对象的拷贝、赋值和销毁行为。理解“三五法则”Rule of Five即如果你需要自定义其中任何一个通常需要全部五个都考虑。这是管理动态资源如堆内存类的生命线错误实现会导致内存泄漏、重复释放等问题。智能指针std::unique_ptr,std::shared_ptr,std::weak_ptrC11起。它们以RAII的方式管理动态内存可以极大地减少手动new/delete带来的内存泄漏和悬空指针问题。现代C应尽量避免使用裸指针进行资源管理。Lambda表达式C11匿名函数对象使得在需要函数对象的地方可以就地定义与STL算法配合使用非常方便如上文find_if中的用法。标准库的深入不仅仅是vector和string还有array定长数组、forward_list单链表、unordered_map哈希表、tuple元组、regex正则表达式等。以及algorithm中上百个通用算法。6.2 常见编译与运行时问题排查链接错误undefined reference to ...最常见原因声明了函数或类但没有定义实现。或者定义了但编译单元.cpp文件没有链接进去。排查检查是否在头文件.h中声明了函数或类但在对应的源文件.cpp中忘记写实现体。检查编译命令是否包含了所有必要的源文件。段错误Segmentation FaultC常见原因解引用空指针或野指针。访问数组越界。使用已释放的内存悬空指针。递归过深导致栈溢出。排查工具使用调试器GDB或VS Debugger运行程序在崩溃时查看调用堆栈和变量值。在Linux下可以用valgrind检查内存错误。内存泄漏表现程序运行时间长了内存占用不断增长。原因new了内存但没有对应的delete。排查与预防使用智能指针这是最根本的解决方法。遵循RAII将资源内存、文件句柄、锁等的获取放在构造函数中释放放在析构函数中。工具在Windows上可以使用Visual Studio的诊断工具在Linux上可以使用valgrind --leak-checkfull。模糊的重载调用错误信息call of overloaded func(...) is ambiguous原因编译器发现多个重载函数都匹配当前的调用参数无法决定用哪一个。void func(int); void func(double); func(10); // 明确调用 func(int) func(3.14); // 明确调用 func(double) func(A); // 模糊char可以提升为int也可以转换为double解决显式进行类型转换如func(static_castint(A))。6.3 给C语言转C学习者的特别建议转变思维从“过程式”思维转向“对象式”和“泛型式”思维。思考如何用类和对象来建模问题如何用模板编写通用代码。拥抱标准库不要再自己写链表、字符串处理函数了。先去熟悉vector,string,map,algorithm。它们比你手写的99%的代码都更高效、更安全。理解对象生命周期C语言中变量作用域结束就没了。C中对象离开作用域时析构函数会被自动调用这是RAII和智能指针工作的基础务必理解透彻。谨慎使用指针能用引用的地方就用引用。需要动态内存时优先考虑智能指针。裸指针主要用于观察所有权不属于自己的对象。从“C with Classes”到现代C不要停留在古老的C98风格。至少以C11作为学习起点了解自动类型推导auto、范围for循环、智能指针、Lambda表达式等现代特性它们能让你的代码更简洁、更安全。实践、实践、再实践看书和看教程只能让你“知道”动手写代码、调试、解决bug才能让你“会”。尝试把以前用C写的小程序用C的风格重写一遍体会其中的差异。

相关新闻