从 C 到 C告别面向过程拥抱对象世界C98/03 入门指南适合人群已经掌握 C 语言基本语法希望快速入门 C经典版的开发者。本文覆盖 C98/03 标准不涉及 C11 及以后的新特性专注于 C 最核心、最稳定的扩展。1. 为什么从 C 过渡到 CC 语言高效、简洁是系统编程的基石。但当项目规模变大时C 的“面向过程”模式容易导致代码耦合度高、复用性差。C 在保留 C 性能优势的同时引入了面向对象和泛型编程帮助程序员更好地组织代码。类型安全C 的cin/cout、new/delete等机制比 C 的标准 I/O 和动态内存管理更安全。代码复用通过类、继承、模板可以写出更通用的代码。数据抽象类将数据和操作绑定隐藏内部实现只暴露接口。多态支持虚函数实现运行时多态让程序更灵活。本文只讨论 C98/03这是所有 C 版本的基础理解它们之后再学习 C11 等新特性会更加轻松。2. 从 C 到 C 的语法“舒适区”C 几乎兼容 C所以你可以把 C 代码直接拿过来编译。同时C 提供了一些让编码更便捷的语法改进。2.1 输入输出告别printf和scanfC 风格C 风格#include stdio.h#include iostreamprintf(%d, x);cout x;scanf(%d, x);cin x;C 的cout和cin会自动识别类型无需格式符且支持链式操作。#includeiostreamusingnamespacestd;intmain(){intage;cout请输入年龄;cinage;cout你的年龄是ageendl;return0;}注意using namespace std;在简单示例中方便但在大型项目中容易引起命名冲突建议只在使用时加std::前缀或使用using std::cout;等。2.2 动态内存管理new和deleteC 使用malloc/freeC 使用new/delete。new不仅分配内存还会调用构造函数后面会说。delete释放内存并调用析构函数。new返回正确类型的指针无需强制转换。int*pnewint(5);// 分配并初始化为 5cout*pendl;deletep;// 释放单个对象int*arrnewint[10];// 分配数组delete[]arr;// 释放数组注意 []2.3 引用Reference指针的“安全替身”引用是变量的别名必须在声明时初始化且不能改变指向。inta10;intrefa;// ref 是 a 的引用ref20;// 等价于 a 20对比指针实现交换函数// C 风格指针voidswap_c(int*x,int*y){inttmp*x;*x*y;*ytmp;}// C 风格引用voidswap_cpp(intx,inty){inttmpx;xy;ytmp;}引用不能为空用起来更直观推荐在函数参数传递时尽量使用const引用避免拷贝。2.4 单行注释//C 支持//注释从此不用再为/* */配对烦恼。3. 函数重载与默认参数3.1 函数重载OverloadC 中函数名必须唯一C 允许同名函数只要参数列表不同类型、个数、顺序。voidprint(inti){cout整数: iendl;}voidprint(doubled){cout浮点数: dendl;}voidprint(constchar*s){cout字符串: sendl;}intmain(){print(10);print(3.14);print(hello);return0;}编译器根据实参类型自动匹配。3.2 默认参数Default Arguments可以为函数参数指定默认值调用时若未提供实参则使用默认值。voidgreet(constchar*name朋友){cout你好nameendl;}intmain(){greet();// 输出你好朋友greet(小明);// 输出你好小明return0;}规则默认参数必须从右向左连续定义即如果某个参数有默认值它右边的所有参数也必须有默认值。3.3 内联函数inlineC 中常用宏函数但宏有副作用如#define SQUARE(x) ((x)*(x))。C 推荐用inline函数编译器会尝试展开避免函数调用开销且类型安全。inlineintsquare(intx){returnx*x;}4. 命名空间Namespace避免名称冲突C 中全局符号都暴露在同一个作用域当项目变大或使用第三方库时名字冲突概率很高。C 通过namespace解决这个问题。namespaceMath{intadd(inta,intb){returnab;}}namespacePhysics{intadd(inta,intb){returnab;}// 同名但不同空间}intmain(){coutMath::add(1,2)endl;// 3coutPhysics::add(1,2)endl;// 3return0;}标准库所有组件都在std命名空间中。5. 字符串与容器STL 初体验C 中处理字符串靠char[]和string.h容易越界。C 提供std::string自动管理内存方便安全。5.1std::string#includestringusingstd::string;intmain(){string s1Hello;string s2World;string s3s1 s2;// 拼接couts3endl;// Hello Worldcout长度: s3.size()endl;couts3[0]endl;// H// 转换为 C 风格字符串用于旧接口constchar*cstrs3.c_str();return0;}5.2 STL 容器vectorvector是一个动态数组可自动扩容替代手动realloc。#includevectorusingstd::vector;intmain(){vectorintv;// 空 vectorv.push_back(10);// 追加v.push_back(20);v.push_back(30);cout元素个数: v.size()endl;cout容量: v.capacity()endl;// 遍历传统下标for(size_t i0;iv.size();i){coutv[i] ;}coutendl;// 使用迭代器更通用for(vectorint::iterator itv.begin();it!v.end();it){cout*it ;}coutendl;return0;}其他常用容器list双向链表、map键值对、set集合。C98 的 STL 已经非常成熟。6. 面向对象基础类与对象这是 C 区别于 C 的最大特性。C 中的结构体只能包含数据而 C 的类可以把数据和操作封装在一起。6.1 类的定义与访问控制classStudent{private:// 私有成员外部无法直接访问string name;intage;doublescore;public:// 公有接口voidsetName(conststringn){namen;}voidsetAge(inta){agea;}voidsetScore(doubles){scores;}voidprint()const{// const 成员函数承诺不修改成员cout姓名: name, 年龄: age, 成绩: scoreendl;}};intmain(){Student stu;stu.setName(张伟);stu.setAge(20);stu.setScore(89.5);stu.print();return0;}structvsclass在 C 中struct默认成员是publicclass默认是private其他完全相同。6.2 构造函数与析构函数构造函数用于初始化对象析构函数用于清理资源如释放动态内存。classStudent{private:string name;intage;double*scores;// 动态数组演示析构函数public:// 构造函数可以重载Student():name(无名),age(0),scores(nullptr){cout默认构造函数endl;}Student(conststringn,inta):name(n),age(a),scores(nullptr){cout带参构造函数endl;}// 析构函数无参无返回值前面加 ~~Student(){cout析构函数: nameendl;if(scores){delete[]scores;scoresnullptr;}}voidsetScores(double*s){scoress;}// ... 其他函数};intmain(){Student s1;// 调用默认构造函数Students2(李华,22);// 调用带参构造函数return0;// 对象离开作用域自动调用析构函数}6.3 拷贝构造函数与赋值运算符简介当我们用一个对象初始化另一个对象时会调用拷贝构造函数用赋值时调用赋值运算符。默认情况下编译器会逐字节拷贝浅拷贝如果成员包含指针则两个对象会指向同一块内存容易造成重复释放。所以需要自定义深拷贝。classStringWrapper{char*data;public:StringWrapper(constchar*str){// 构造函数datanewchar[strlen(str)1];strcpy(data,str);}// 拷贝构造函数深拷贝StringWrapper(constStringWrapperother){datanewchar[strlen(other.data)1];strcpy(data,other.data);}// 赋值运算符深拷贝StringWrapperoperator(constStringWrapperother){if(this!other){// 防止自我赋值delete[]data;datanewchar[strlen(other.data)1];strcpy(data,other.data);}return*this;}~StringWrapper(){delete[]data;}};这是 C 中经典的“三/五法则”需要析构、拷贝构造、赋值运算符同时存在在初学阶段理解即可。6.4this指针每个非静态成员函数都有一个隐含的this指针指向当前对象。在函数内部可以显式使用例如区分参数和成员同名时voidsetName(conststringname){this-namename;// this-name 是成员变量name 是参数}7. 继承与多态面向对象核心7.1 继承Inheritance继承允许我们基于已有类创建新类复用代码。classPerson{protected:// 派生类可访问string name;intage;public:Person(conststringn,inta):name(n),age(a){}voiddisplay()const{coutPerson: nameendl;}};classStudent:publicPerson{// public 继承private:intgrade;public:Student(conststringn,inta,intg):Person(n,a),grade(g){}// 调用基类构造函数voiddisplay()const{// 重写overrides基类函数coutStudent: name, 年级: gradeendl;}};intmain(){Students(王芳,18,3);s.display();// 调用 Student 的 displayreturn0;}三种继承方式public保持基类访问权限、protected基类 public/protected 变为 protected、private基类所有成员变为 private。实际开发中几乎只用public继承。7.2 虚函数与多态Polymorphism如果要让基类指针或引用调用派生类的重写函数必须将基类函数声明为virtual。classAnimal{public:virtualvoidspeak()const{cout动物叫endl;}};classDog:publicAnimal{public:virtualvoidspeak()const{cout汪汪endl;}// virtual 可省略};classCat:publicAnimal{public:virtualvoidspeak()const{cout喵喵endl;}};voidmakeSound(constAnimala){a.speak();// 运行时决定调用哪个版本}intmain(){Dog d;Cat c;makeSound(d);// 汪汪makeSound(c);// 喵喵return0;}虚函数表vtable编译器通过虚函数表实现动态绑定简单理解就是每个对象会隐藏一个指向虚函数表的指针。纯虚函数与抽象类classShape{public:virtualvoiddraw()const0;// 纯虚函数无实现};classCircle:publicShape{public:virtualvoiddraw()const{cout画圆endl;}};含有纯虚函数的类称为抽象类不能实例化只能作为基类。7.3 虚析构函数如果基类的析构函数不是虚的当通过基类指针删除派生类对象时只会调用基类析构导致派生类部分未被释放造成内存泄漏。classBase{public:virtual~Base(){}// 虚析构};classDerived:publicBase{int*data;public:Derived(){datanewint[100];}~Derived(){delete[]data;}};intmain(){Base*pnewDerived();deletep;// 正确调用 Derived 析构然后 Base 析构return0;}对比 C在 C 中实现类似多态需要函数指针和手动类型判断C 通过虚函数机制让多态成为语言层面的特性。8. 泛型编程初探——模板Templates模板允许编写与类型无关的代码是 STL 的基础。8.1 函数模板templatetypenameTTmax(T a,T b){return(ab)?a:b;}intmain(){coutmax(3,5)endl;// 5coutmax(3.14,2.71)endl;// 3.14coutmax(a,z)endl;// zreturn0;}编译器会根据实参类型实例化具体的函数。8.2 类模板templatetypenameTclassBox{T value;public:Box(constTv):value(v){}Tget()const{returnvalue;}};intmain(){Boxintb1(123);Boxstringb2(hello);coutb1.get()endl;coutb2.get()endl;return0;}vectorT就是一个类模板。编译方式模板定义通常放在头文件中因为编译器需要看到完整定义才能实例化。8.3 模板特化简单提及可以为特定类型提供定制实现但不建议初学深入。9. 异常处理可选但建议了解C 中错误处理常用返回值或setjmp/longjmp不够安全。C 提供try/throw/catch。#includestdexceptdoubledivide(doublea,doubleb){if(b0){throwstd::runtime_error(除数不能为零);}returna/b;}intmain(){try{doubleresultdivide(10,0);coutresultendl;}catch(conststd::exceptione){cerr捕获异常: e.what()endl;}return0;}异常会沿着调用栈向上传播直到被捕获。配合 RAII资源获取即初始化可以自动释放资源比 C 的错误处理更可靠。10. 总结与学习建议10.1 学习路线先熟悉 C 的“更好 C”部分引用、new/delete、重载、默认参数、string、vector。再学习面向对象类、继承、多态但不要一开始就过度设计先学会用类封装数据和简单行为。掌握 STL 常用容器和算法vector、list、map、string以及迭代器和算法如sort。了解模板和异常为后续学习泛型编程和现代 C 打下基础。10.2 推荐书目《C Primer》第4版对应 C98/03——经典入门教材讲解细致。《Effective C》——收录 50 条具体建议提升编程素养。10.3 常见陷阱提醒忘记将基类析构函数设为虚→ 导致派生类资源未释放。在头文件中使用using namespace std;→ 污染全局命名空间建议仅在.cpp文件或局部作用域使用。默认拷贝构造/赋值导致浅拷贝→ 若类管理动态内存必须自定义深拷贝。模板定义放在.cpp文件→ 导致链接错误应放在头文件。附录代码示例汇总建议你在本地编译运行以下片段亲身体验 C 的魅力使用new/delete管理动态数组对比malloc/free。用vector替代 C 中的变长数组VLA。用string处理字符串避免strcpy等危险函数。实现一个简单的栈类Stack用动态数组存储包含构造、析构、push、pop。创建一个图形类层次Shape抽象基类→Circle、Rectangle实现draw()并利用多态绘制不同形状。结语从 C 到 C不仅是学习新的语法更是思维方式的转变。从面向过程到面向对象从手动管理到自动管理C 为大型软件开发提供了更强有力的工具。希望本文能帮你顺利迈出第一步之后再去探索 C11 及以后的新特性时你会发现它们都是在这些基础上进一步优化和扩展的。