C++ 面向对象、构造函数、初始化列表、引用、string
一、面向过程与面向对象对比
1. 核心思想差异
- 面向过程:以“步骤”为核心,按顺序执行操作。例如操作文件需依次调用
open→read/write→close,开发者需关注每一步的实现细节。 - 面向对象:以“对象”为核心,对象包含属性(数据)和方法(操作)。操作文件时,文件作为对象,自带
打开/读写/关闭方法,开发者只需调用对象的方法,无需关注内部实现。
2. 代码示例对比(以 LCD 操作为例)
- 面向对象风格(简洁高效,无需关注初始化细节):
MyLcd l;// 创建 LCD 对象(自动完成初始化)l.DrawRectangle(200,100);// 调用对象方法画矩形l.DrawCircle(300,700,300);// 调用对象方法画圆l.LcdClose();// 调用对象方法关闭设备
3. 优缺点总结
- 面向对象:开发效率高、代码易维护,但对象创建有轻微性能开销。
- 面向过程:执行效率略高,但代码复用性差、复杂项目维护难度大。
二、构造函数(Constructor)
1. 核心作用
对象实例化时自动调用,用于初始化对象的成员变量,解决“对象使用前必须初始化”的问题(如 LCD 设备需先打开才能使用)。
2. 语法规则
- 无返回值(连
void都不能写),函数名与类名完全一致。 - 支持重载(可定义多个参数不同的构造函数)。
- 若未手动定义,编译器会自动生成一个“无参、无功能”的默认构造函数。
- 一旦手动定义任意构造函数,编译器不再自动生成默认构造函数。
3. 常见用法
#include<iostream>#include<cstring>usingnamespacestd;classPerson{private:intage;charname[32];public:// 1. 无参构造(默认构造)Person(){age=0;name[0]='\0';// 字符串初始化(避免垃圾值)}// 2. 单参构造(int 类型)Person(inta){age=a;name[0]='\0';}// 3. 单参构造(字符串类型)Person(constchar*n){age=0;strncpy(name,n,sizeof(name)-1);// 安全拷贝,避免数组越界name[sizeof(name)-1]='\0';}// 4. 多参构造Person(inta,constchar*n){age=a;strncpy(name,n,sizeof(name)-1);name[sizeof(name)-1]='\0';}// 5. 带默认参数的构造函数Person(inta=18,constchar*n="unknown"){age=a;strncpy(name,n,sizeof(name)-1);name[sizeof(name)-1]='\0';}};4. 对象实例化与构造函数调用
- 推荐使用
{}初始化(C++11 新标准,避免歧义):Person p1;// 调用无参构造Person p2{18};// 调用单参构造(int)Person p3{"penglei"};// 调用单参构造(字符串)Person p4{20,"ZhangSan"};// 调用多参构造Person p5{22,"LiSi"};// 调用带默认参数的构造(覆盖默认值) - 注意:
Person p();会被编译器识别为“函数声明”,而非对象实例化,需避免。
5. 访问权限
- 构造函数通常设为
public(允许外部实例化对象)。 - 特殊场景设为
private(如单例模式,限制对象创建数量)。
6. 练习:People 类设计
classPeople{private:intage;string name;// 后续改用 string 类,更安全便捷string addr;public:// 无参构造People():age(0),name("unknown"),addr("unknown"){}// 全参构造People(inta,conststring&n,conststring&ad):age(a),name(n),addr(ad){}// 打印信息voidShowInfo(){cout<<"Age: "<<age<<", Name: "<<name<<", Addr: "<<addr<<endl;}};// 调用示例intmain(){People p1;p1.ShowInfo();// 输出:Age: 0, Name: unknown, Addr: unknownPeople p2{25,"WangWu","Beijing"};p2.ShowInfo();// 输出:Age: 25, Name: WangWu, Addr: Beijingreturn0;}三、析构函数(Destructor)
1. 核心作用
对象生命周期结束时自动调用,用于清理资源(如关闭文件、释放动态内存、解绑设备等),避免资源泄漏。
2. 语法规则
- 无返回值,无参数(因此无法重载,一个类只能有一个析构函数)。
- 函数名前加
~,与类名一致。 - 若未手动定义,编译器会自动生成一个“无功能”的默认析构函数。
3. 用法示例
classMyLcd{private:intfd=-1;// 文件描述符unsignedint*plcd=nullptr;// 内存映射指针public:// 构造函数:打开 LCD 设备MyLcd(){fd=open("/dev/fb0",O_RDWR);if(fd==-1){cerr<<"LCD open failed!"<<endl;return;}// 内存映射等初始化操作...cout<<"LCD initialized!"<<endl;}// 析构函数:关闭设备、释放资源~MyLcd(){if(plcd!=nullptr){munmap(plcd,800*480*4);// 释放内存映射plcd=nullptr;}if(fd!=-1){close(fd);// 关闭文件fd=-1;}cout<<"LCD resource released!"<<endl;}};4. 调用顺序
- 遵循“先构造,后析构”原则:先创建的对象最后被销毁,后创建的对象先被销毁。
- 示例:
intmain(){MyLcd l1;// 构造 l1MyLcd l2;// 构造 l2// 生命周期结束时,先析构 l2,再析构 l1return0;}// 输出:// LCD initialized!// LCD initialized!// LCD resource released!// LCD resource released!
5. 注意事项
- 析构函数仅用于“清理资源”,不建议写入业务逻辑。
- 避免在构造函数/析构函数中使用
return(可能导致初始化/清理不完整)。
四、构造函数初始化列表
1. 核心用途
解决“构造函数体内无法初始化某些成员”的问题,例如:
- 常量成员(
const修饰,必须初始化且不可修改)。 - 引用成员(必须在定义时初始化)。
- 父类无默认构造函数时,需通过初始化列表传递参数。
- 效率更高:直接初始化成员变量,而非先默认构造再赋值。
2. 语法规则
类名(参数列表):成员变量1(初始值1),成员变量2(初始值2),...{// 构造函数体(可选,用于后续逻辑处理)}- 初始值可来自构造函数参数、常量或表达式。
- 成员变量初始化顺序与“声明顺序”一致,与初始化列表中的顺序无关。
3. 用法示例
classStudent{private:constintid;// 常量成员(必须初始化)int&scoreRef;// 引用成员(必须初始化)string name;public:// 初始化列表初始化常量、引用、普通成员Student(intstudentId,int&score,conststring&studentName):id(studentId),scoreRef(score),name(studentName){// 构造函数体可省略(无额外逻辑时)}voidShow(){cout<<"ID: "<<id<<", Name: "<<name<<", Score: "<<scoreRef<<endl;}};// 调用示例intmain(){intmathScore=95;Student stu{1001,mathScore,"ZhaoLiu"};stu.Show();// 输出:ID: 1001, Name: ZhaoLiu, Score: 95mathScore=98;// 修改被引用的变量stu.Show();// 输出:ID: 1001, Name: ZhaoLiu, Score: 98(引用同步变化)return0;}4. 注意事项
- 若成员变量是数组(如
char name[32]),初始化列表无法直接赋值,需在构造函数体内用strcpy等函数处理。 - 推荐优先使用初始化列表初始化成员变量,尤其是常量、引用和自定义类型成员。
五、引用(Reference)
1. 核心概念
引用是变量的“别名”,与原变量指向同一块内存空间,操作引用等价于操作原变量。用于替代指针,解决指针的野指针、空指针等安全问题。
2. 语法规则
- 定义格式:
类型& 引用名 = 原变量;(必须在定义时初始化)。 - 引用类型必须与原变量类型一致(或存在赋值兼容,如子类引用指向父类对象)。
- 定义后,引用不能重新绑定到其他变量。
3. 基础用法
inta=1024;int&b=a;// b 是 a 的别名,与 a 共享内存b=2048;// 修改 b 等价于修改 acout<<a<<endl;// 输出:2048cout<<&a<<" "<<&b<<endl;// 地址相同4. 核心用途
(1)函数参数传递(替代指针,更安全)
// 引用版 swap 函数(无需解引用,简洁安全)voidswap(int&a,int&b){inttemp=a;a=b;b=temp;}// 调用示例intmain(){intx=10,y=20;swap(x,y);// 直接传变量,无需传地址cout<<x<<" "<<y<<endl;// 输出:20 10return0;}(2)函数返回值(避免拷贝,提升效率)
- 注意:禁止返回局部变量的引用(局部变量生命周期结束后释放,引用会变成“悬空引用”)。
- 允许返回:静态变量、全局变量、类成员变量、传入的引用参数。
// 正确示例:返回静态变量的引用int&getStaticValue(){staticintval=0;// 静态变量,生命周期与程序一致val++;returnval;}// 正确示例:返回传入的引用参数string&appendStr(string&s,conststring&suffix){s+=suffix;returns;}// 调用示例intmain(){cout<<getStaticValue()<<endl;// 输出:1cout<<getStaticValue()<<endl;// 输出:2string str="Hello";appendStr(str," World");cout<<str<<endl;// 输出:Hello Worldreturn0;}5. 引用与指针的区别(必须掌握)
| 特性 | 引用 | 指针 |
|---|---|---|
| 初始化 | 定义时必须初始化 | 可定义时不初始化(野指针风险) |
| 空值 | 无空引用(更安全) | 有空指针(nullptr) |
| 重新绑定 | 一旦绑定,无法修改 | 可随时改变指向的变量 |
| 内存开销 | 不占用额外内存(仅别名) | 占用内存(存储变量地址) |
| 多级使用 | 无多级引用(int&& a是右值引用,非多级) | 有多级指针(int** p) |
| 安全性 | 高(无野指针、无需解引用) | 低(需手动管理,避免野指针) |
| 传参/返回值 | 简洁直观,无需解引用 | 需用*解引用,&取地址 |
六、string 类(字符串处理)
1. 核心优势
替代 C 语言的char*字符串,解决数组越界、手动管理内存等问题,提供丰富的字符串操作接口,安全且便捷。
2. 头文件
#include<string>// 注意:不是 <string.h>(C 语言头文件)usingnamespacestd;// 或 using std::string;3. 构造函数(常用)
// 1. 无参构造(空字符串)string s1;// 2. 用字符构造(n 个 ch)strings2(5,'a');// s2 = "aaaaa"// 3. 用 C 风格字符串构造strings3("hello");// s3 = "hello"// 4. 用 C 风格字符串的前 n 个字符构造strings4("hello world",5);// s4 = "hello"// 5. 用初始化列表构造string s5{'h','e','l','l','o'};// s5 = "hello"// 6. 拷贝构造(用其他 string 对象构造)strings6(s3);// s6 = "hello"string s7=s3;// 等价于 s64. 赋值操作(operator=)
string s;s='A';// 赋值单个字符s="hello";// 赋值 C 风格字符串string s2="world";s=s2;// 赋值 string 对象s+="!";// 追加字符串(operator+= 重载)5. 字符串拼接(operator+)
string s1="Hello";string s2="World";string s3=s1+" "+s2;// s3 = "Hello World"6. 字符串比较(重载运算符)
支持==、!=、<、>、<=、>=,按字典序比较:
string a="apple";string b="banana";cout<<(a<b)<<endl;// 输出:1(true,"apple" 字典序小于 "banana")cout<<(a=="apple")<<endl;// 输出:1(true)7. 字符串访问(常用接口)
string s="abcdefg";// 1. operator[]:无越界检查(效率高)charc1=s[2];// c1 = 'c's[3]='X';// s = "abcefg" → 修正:s 变为 "abcXefg"// 2. at():有越界检查,越界抛异常(安全)charc2=s.at(4);// c2 = 'e'try{s.at(10);// 越界,抛 std::out_of_range 异常}catch(constexception&e){cerr<<e.what()<<endl;}// 3. front() / back():获取首/尾字符charfirst=s.front();// 'a'charlast=s.back();// 'g'// 4. data() / c_str():返回 C 风格字符串(const char*)constchar*cStr=s.c_str();// 用于兼容 C 语言接口(如 open、printf)printf("%s\n",cStr);// 输出:abcXefg8. 容量与大小(常用接口)
string s="hello";cout<<s.size()<<endl;// 输出:5(字符个数)cout<<s.length()<<endl;// 输出:5(与 size() 等价)cout<<s.empty()<<endl;// 输出:0(false,非空)cout<<s.max_size()<<endl;// 输出:字符串最大可容纳的字符数(系统相关)s.clear();// 清空字符串(size() 变为 0)cout<<s.empty()<<endl;// 输出:1(true)9. 字符串操作(常用接口)
string s="hello";// 1. 追加s.push_back('!');// 尾部追加字符 → "hello!"s+=" world";// 尾部追加字符串 → "hello! world"// 2. 插入s.insert(5,",");// 在索引 5 处插入字符 → "hello,! world"// 3. 删除s.erase(5,1);// 从索引 5 开始,删除 1 个字符 → "hello! world"// 4. 查找size_t pos=s.find("world");// 查找子串,返回起始索引(6)if(pos!=string::npos){// 找到返回索引,未找到返回 string::nposcout<<"Found at: "<<pos<<endl;}// 5. 替换s.replace(6,5,"C++");// 从索引 6 开始,替换 5 个字符 → "hello! C++"// 6. 截取子串string sub=s.substr(6,3);// 从索引 6 开始,截取 3 个字符 → "C++"10. 兼容 C 语言接口
当需要调用 C 语言函数(如open、fopen)时,用c_str()转换为const char*:
string path="/mnt/share/test.txt";// 调用 C 语言 open 函数intfd=open(path.c_str(),O_RDWR);if(fd==-1){perror("open failed");}