C++基础(一)
•定义命名空间,在一个头文件里面创建了一个命名空间double area(double r){//求圆的面积double getLength(double r){//求圆的周长•使用命名空间int main()//强制控制台用UTF-8编码" << endl;double r;cout << "请输入半径:" << endl;cin >> r;cout << "对应的周长是:" << getLen
·
1. 联系
C和C++之间的关系是紧密且复杂的。C++最初是作为C语言的一个扩展开发的,目的是在不放弃C的强大功能和效率的同时,增加对象导向编程、泛型编程和其他一些特性。下面是C和C++之间主要的关系和区别:
1. 兼容性:C++在很大程度上是与C兼容的。这意味着许多C程序可以在C++编译器中编译并运行,尽管可能需要一些小的修改。
2. 面向对象编程(OOP):C++引入了面向对象编程。它允许使用类和对象,而C是一个过程性语
言,不支持这些概念,或者说支持的不好,麻烦。
3. 模板:C++支持模板,这是一种允许程序员编写与数据类型无关的代码的功能。C没有这个功能。
4. 标准库:C++有一个更丰富的标准库,包括STL(标准模板库),这为数据结构和算法提供了广泛的支持。而C的标准库相对较小。
5. 类型检查:C++比C提供更严格的类型检查。这意味着某些在C中可行但可能导致错误的代码在
C++中可能无法编译。
6. 异常处理:C++支持异常处理,这是一种处理程序运行时错误的机制。C没有内置的异常处理机制。
7. 命名空间:C++引入了命名空间,这有助于防止名称冲突。C没有这个概念。
2. 命名空间
2.1.1 命名空间作用
• 创建自己的命名空间是 C++ 中组织代码的一种好方法,特别是在开发大型项目或库时。命名空间可以帮助你避免名称冲突,并且清晰地组织代码。
• std是 C++ 标准库的命名空间。它是一个定义在C++标准库中的所有类、函数和变量的命名空间。
• 新建一个C++工程默认有的代码是
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!" << endl;
return 0;
}
• 在C++中,如果你想使用std标准库中的任何类、函数或对象,你通常有两种选择:
1. 使用 std:: 前缀:这是最常见的方式,它明确指定了你正在使用的是位于 std 命名空间中的元
素。
std::cout << "Hello, world!" << std::endl;
2. 使用using namespace std; 这允许你在不显式指定std::的情况下使用 std 命名空间中的
所有元素。
using namespace std;
cout << "Hello, world!" << endl;
• std包含的内容:
• std 命名空间包含了许多类、函数和对象,例如:
• 输入输出库(如 std::cout , std::cin , std::endl )
• 容器类(如 std::vector , std::map , std::set )
• 字符串类( std::string )
• 异常类( std::exception 和相关子类)
• 算法(如 std::sort , std::find )
• 实用工具(如 std::pair , std::tuple )
• 其他许多功能
2.1.2 自定义命名空间
• 定义命名空间,在一个头文件里面创建了一个命名空间
#ifndef CIR_H
#define CIR_H
namespace Cir {
double pi = 3.14;
double area(double r){//求圆的面积
return pi * r * r;
}
double getLength(double r){//求圆的周长
return 2 * pi * r;
}
}
#endif // CIR_H
• 使用命名空间
#include <iostream>
#include "Cir.h"
using namespace std;
using namespace Cir;
int main()
{
system("chcp 65001 > nul");//强制控制台用UTF-8编码
cout << "Hello World!" << endl;
double r ;
cout << "请输入半径:" << endl;
cin >> r;
cout << "对应的周长是:" << getLength(r) << ",对应的面积是:" << area(r) << endl;
return 0;
}
• 通过使用自定义命名空间,你可以有效地组织你的代码,并减少不同库之间的名称冲突。
3. 基础
3.1 输入输出
• 常用的是 iostream 库,它提供了用于输入和输出的基本流类,包括 cin,cout,cerr和clog 。
• 标准错误流 ( cerr ) 和标准日志流 ( clog )
• cerr用于输出错误消息。与cout不同, cerr 不是缓冲的,这意味着它会立即输出。
• clog类似于cerr ,但它是缓冲的。它通常用于记录错误和日志信息。
3.2 <climits>这个头文件
• 用途:查询数据类型的取值范围
#include <climits>//引用了这个头文件之后
• 然后,可以使用它提供的各种常量,例如:
• INT_MAX : int 类型的最大值。
• INT_MIN : int 类型的最小值。
• UINT_MAX : unsigned int 类型的最大值。
• LONG_MAX : long int 类型的最大值。
• LONG_MIN : long int 类型的最小值。
• LLONG_MAX : long long int 类型的最大值。
• LLONG_MIN : long long int 类型的最小值。
3.3 内联函数
• 内联函数(Inline Function)是C++中一种特殊的函数,其定义直接在每个调用点展开。这意味着编译器会尝试将函数调用替换为函数本身的代码,这样可以减少函数调用的开销,尤其是在小型函数中。
• 特点
1. 减少函数调用开销:内联函数通常用于优化小型、频繁调用的函数,因为它避免了函数调用的常规开销(如参数传递、栈操作等)。
2. 编译器决策:即使函数被声明为内联,编译器也可能决定不进行内联,特别是对于复杂或递归函数。
3. 适用于小型函数:通常只有简单的、执行时间短的函数适合做内联。
4. 定义在每个使用点:内联函数的定义(而非仅仅是声明)必须对每个使用它的文件都可见(必须是完整的代码),通常意味着将内联函数定义在头文件中。
• 使用方法
//通过在函数声明前添加关键字inline来指示编译器该函数适合内联
inline int max(int x, int y) {
return x > y ? x : y;
}
• 例子
#include <iostream>
using namespace std;
inline int add(int a,int b){
return a + b;
}
int main()
{
cout << "Hello World!" << endl;
int sum = add(3,5);//编译器可能会将此替换为:int sum = a +b = 5 + 3;
cout << "sum:" << sum << endl;
return 0;
}
• 注意事项:
• 过度使用的风险:不应滥用内联函数,因为这可能会增加最终程序的大小(代码膨胀)。对于大型函数或递归函数,内联可能导致性能下降。
• 编译器的决定:最终是否将函数内联是由编译器决定的,即使函数被标记为 inline 。
• 适用场景:最适合内联的是小型函数和在性能要求高的代码中频繁调用的函数。
3.4 Lambda表达式
• Lambda表达式是C++11引入的一种匿名函数的方式,它允许你在需要函数的地方内联地定义函数,而无需单独命名函数。
• Lambda 表达式的基本语法如下:
[capture clause](parameters) -> return_type {
// 函数体
// 可以使用捕获列表中的变量
return expression; // 可选的返回语句
}
• Lambda 表达式由以下部分组成:
• 捕获列表(Capture clause):用于捕获外部变量,在 Lambda 表达式中可以访问这些变量。捕
获列表可以为空,也可以包含变量列表 [var1, var2, ...] 。
• 参数列表(Parameters):与普通函数的参数列表类似,可以为空或包含参数列表(param1, param2, ...) 。
• 返回类型(Return_type):Lambda表达式可以自动推断返回类型auto,也可以显式指定返回类
型 -> return_type 。如果函数体只有一条返回语句,可以省略返回类型。
• 函数体(Body):Lambda 表达式的函数体,包含需要执行的代码。
• 示例1:使用 Lambda 表达式进行加法,作为一种简洁而快速的方式来定义小型函数
#include <iostream>
using namespace std;
int main()
{
//cout << "Hello World!" << endl;
//定义一个简单的Lambda表达式进行加法
auto sum = [](int a,int b)-> int {//这是lambad表达式,但是此时如果不用回调调用的话,是没法调用的
//所以此时需被迫加上名字(显示取了一个名字)
return a + b;
};
//使用Lambda表达式计算两个数的和
int ret = sum(5,3);//有点“函数嵌套”的感觉。
cout << "ret:" << ret;
return 0;
}
• 示例2:使用一个函数来找出两个数中的较大数,这个函数将接受一个lambda函数作为回调来比较这两个数。Lambda函数将直接在函数调用时定义,完全是匿名的。
• 先使用函数指针作为参数的例子。
#include <iostream>
using namespace std;
bool myCompare(int a,int b){
return a > b;
}
int getMaxValue(int a,int b,bool (*p1)(int , int)){
if(p1(a,b)){
return a;
}else {
return b;
}
}
int main()
{
int max = getMaxValue(8,5,myCompare);
cout << "MAX_Value:" << max << endl;
return 0;
}
• 然后再使用lamdba表达式作为参数的例子。
#include <iostream>
using namespace std;
int getMaxValue(int a,int b,bool (*p1)(int , int)){
if(p1(a,b)){
return a;
}else {
return b;
}
}
int main()
{
int a = 8,b = 16;
//直接在函数调用中定义匿名 lambda 函数
int max = getMaxValue(a,b,[](int a,int b){
return a > b;
});
cout << "MAX_Value:" << max << endl;
return 0;
}
• 参数捕获:在 Lambda 表达式中,参数捕获是指 Lambda 表达式从其定义的上下文中捕获变量的能力。这使得Lambda可以使用并操作在其外部定义的变量。捕获可以按值(拷贝)或按引用进行。
• 示例:使用带参数捕获的 Lambda 表达式
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int y = 20;
//捕获 x 和 y 以便在 Lambda 内部使用
//这里的捕获列表 [x, y] 表示 x 和 y 被按值捕获
auto sum = [x,y](){
// y++;
// x++;会报错,按值捕获,关注的是值本身,无法修改
return x + y;
};
cout << "Sum is: " << sum() << endl;
cout << "x is now: " << x << ", y is now: " << y << endl;
//捕获所有外部变量按值捕获(拷贝)
int z = 30;
auto mul = [=](){
// z++;会报错,按值捕获,关注的是值本身,无法修改
return x * y * z;
};
cout << "mul is: " << mul() << endl;
// 捕获所有外部变量按引用捕获
auto modifyAndSum = [&](){
x = 15;//修改x的实际值
y = 50;//修改y的实际值, 引用捕获可以修改
return z * x * y;
};
cout << "modifyAndSum is: " << modifyAndSum() << endl;
cout << "x is now: " << x << ", y is now: " << y << ", z is now: " << z << endl;
return 0;
}
• 内联函数和lambda表达式的区别
| 特性 | Lambda表达式 | 内联函数 |
| 定义 |
一种匿名函数,通常用于定义在需要它们的地方。
|
一种常规函数,通过inline 关键字定义。
|
| 用途 |
提供一种快捷方式来定义临时的、小型的函数。
|
用于优化小型函数,减少函数调用的开销。
|
| 语法 |
使用 [capture](params) { body }
的形式定义。
|
使用常规函数定义语法,但在前面加上inline 关键字。
|
| 生命周期 |
在定义它们的作用域内有效。
|
在整个程序执行期间有效。
|
|
捕获外部变量
|
可以捕获外部作用域中的变量(按值或按引用)。
|
不能直接捕获外部变量,只能通过参数传递。
|
| 调用方式 |
作为函数对象,可直接调用。
|
像普通函数一样调用。
|
| 优化 |
可以被编译器自动内联化,但这取决于编译器优化策略。
|
明确请求编译器尝试内联,但实际内联化也取决于编译器。
|
| 可见性 |
通常只在定义它们的局部作用域内可见。
|
可以在定义它的任何作用域内可见。
|
| 使用场景 |
适合于一次性使用的场景,如作为回调、 在算法中使用等。
|
适合于频繁调用的小型函数。
|
3.5 字符串string类型
• C语言中对字符串的表示通常用指针,但是可能会面临内存泄漏或者段错误等众多问题。
• 在 C++ 中,string类是标准库的一部分,用于表示和操作字符串。它是对传统的C风格字符串(以空字符 '\0' 结尾的字符数组)的一个更安全、更方便的封装。 string 类是在<string>头文件中定义的,并且位于 std 命名空间中。
• string类提供了许多有用的功能和特性,包括:
1. 动态大小:与C风格的字符串不同, string对象的字符串可以动态改变大小,这意味着你可以在运行时添加或移除字符,而不需要担心分配和释放内存。
2. 安全性:由于string管理自己的内存,因此减少了内存泄漏和缓冲区溢出的风险。
3. 方便的成员函数:string类提供了各种操作字符串的方法,如 append() (添加),insert()(插入)、 erase() (删除), substr()(获取子字符串)等。
4. 操作符重载:string类重载了多个操作符,使得字符串比较、连接和赋值更加直观。例如,你可
以使用 + 操作符来连接两个字符串,或者使用 == 操作符来比较两个字符串是否相等。
5. 迭代器支持:像其他标准库容器一样, string 类也支持迭代器,使得你可以使用迭代器来遍历字
符串中的字符。
6. 与 C 风格字符串的兼容性:string类提供了与C风格字符串互操作的功能,例如,你可以使用
c_str() 方法来获取一个与C风格字符串兼容的、以null结尾的字符数组。
• 常用字符串函数
| 函数名 | 功能 | 参数 | 返回值类型 |
|
length() 或 size()
|
返回字符串的长度
|
无 |
size_t
|
|
empty()
|
检查字符串是否为空
|
无
|
bool
|
|
append(const string& str)
|
向字符串末尾添加另一个字符串
|
要追加的字符串
|
string&
|
|
substr(size_t pos = 0, size_t len = npos)
|
返回一个子字符串
|
pos:子字符串 的起始位置
len:子字符串的长度
|
string
|
|
find(const string& str,size_t pos = 0)
|
查找子字符串出 现的位置
|
str:要查找的
字符串
pos:搜索起始位置
|
size_t
|
|
compare(const string& str)
|
比较两个字符串
|
要比较的字符串
|
int
|
|
erase(size_t pos = 0, size_t len = npos)
|
删除字符串中的 一部分
|
pos:起始位置
len:要删除的 长度
|
string&
|
|
insert(size_t pos, const string& str)
|
在指定位置插入 字符串
|
pos:插入位置
str:要插入的字符串
|
string&
|
|
replace(size_t pos, size_t len, const string& str)
|
替换字符串中的
一部分
|
pos:起始位置
len:要替换的 长度
str:替换的字符串
|
string&
|
|
c_str()
|
返回 C 风格字符串表示
|
无 |
const char*
|
|
operator[] (size_t pos)
|
访问指定位置的
字符
|
pos:字符位置
|
char&
|
• 示例
#include <iostream>
#include <string>
using namespace std;
int main()
{
system("chcp 65001 > nul");//强制控制台用UTF-8编码
string str = "hello";
if(!str.empty()){
cout << "该字符串不为空,长度为:" << str.length() << endl;//长度为5
}
str.append(",world");
cout << "拼接后的字符串为:" << str << endl;
//cout << "查找到子串为" << str.substr(0,6);
//cout << "子串首次出现的位置是" << str.find("ll",0) << endl;
// string strT = "hello";
// cout << str.compare(strT) << endl;
// strT.append(",world");
// cout << str.compare(strT) << endl;//0
// cout << "删除的字符串为:" << str.erase(5,6) << endl;
// cout << "删除之后的字符串为:" << str << endl;
// str.insert(5,",world");
// cout << "insert之后的字符串为:" << str << endl;
// str.replace(5,6,"xiaole");
// cout << "replace之后的字符串为:" << str << endl;
// const char * s = str.c_str();
// printf("c风格的字符串为%s\r\n",s);
cout << "当前字符为:" << str.operator[](2) << endl;
return 0;
}
4. 类
4.1 类的初识
• 在C++中的类(class)是一种编程结构,用于创建对象。这些对象可以拥有属性(即数据成员)和行为(即成员函数或方法)。类的概念是面向对象编程的核心之一,其主要目的是将数据和与数据相关的操作封装在一起。例如,如果你有一个“汽车”类,它可能包含颜色、品牌、型号等属性(数据成员),以及启动、停止、加速等行为(成员函数)。每当你基于这个类创建一个对象时,你就有了一个具体的汽车,具有这些属性和行为。
• 类的基本结构通常包含:
1. 数据成员(Attributes):定义类的属性。这些是类内部的变量,用于存储对象的状态。
2. 成员函数(Methods):定义类的行为。这些是可以操作对象的数据成员的函数。
3. 构造函数和析构函数:特殊的成员函数。构造函数在创建对象时自动调用,用于初始化对象。析构 函数在对象销毁时调用,用于执行清理操作。
4. 访问修饰符:如public , private , protected ,用于控制对类成员的访问权限。例如,public成员可以在类的外部访问,而 private 成员只能在类内部访问。
5. 继承:允许一个类继承另一个类的特性。这是代码重用和多态性的关键。
4.2 组合
• 在C++中,一个类包含另一个类的对象称为组合(Composition)。这是一种常见的设计模式,用于表示一个类是由另一个类的对象组成的。这种关系通常表示一种"拥有"("has-a")的关系。
#include <iostream>
#include <string>
using namespace std;
class Wheel
{
public:
string brand;
int year;
void wheelPrintInfo();
};
void Wheel::wheelPrintInfo()
{
cout << "我的轮胎品牌是:" << brand << endl;
cout << "我的轮胎日期是:" << year << endl;
}
//在C++中,一个类包含另一个类的对象称为组合(Composition)
class Car{ //汽车“类”
public:
string color; //颜色
string brand; //品牌
string type; //车型
int year; //年限
Wheel wl;
Wheel *pwl;
//函数指针,指向车介绍函数
//其实也是成员数据,指针变量,指向函数的变量,并非真正的成员函数
//保存某个函数地址的变量
void (*printCarInfo)(string color,string brand,string type, int year);
void (*carRun)(string type); //函数指针,指向车运行的函数
void (*carStop)(string type); //函数指针,执行车停止的函数
void realPrintCarInfo();//声明成员函数
};
void Car::realPrintCarInfo(){
string str = "车的品牌是:" + brand
+ ",型号是: " + type
+ ",颜色是:" + color
+ ",上市年限是:" + std::to_string(year);
cout << str << endl;
}
void bwmThreePrintCarInfo(string color,string brand,string type, int year)
{
string str = "车的品牌是:" + brand
+ ",型号是: " + type
+ ",颜色是:" + color
+ ",上市年限是:" + std::to_string(year);//通过std::tostring()函数,将整型数转化成字符串
cout << str << endl;
}
void A6PrintCarInfo(string color,string brand,string type, int year)
{
string str = "车的品牌是:" + brand
+ ",型号是: " + type
+ ",颜色是:" + color
+ ",上市年限是:" + std::to_string(year);
cout << str << endl;
}
int main()
{
system("chcp 65001 > nul");//强制控制台用UTF-8编码
Car BWMthree;
BWMthree.color = "白色";
BWMthree.brand = "宝马";
BWMthree.type = "3系";
BWMthree.year = 2023;
BWMthree.wl.year = 2023;
BWMthree.wl.brand = "米其林";
BWMthree.wl.wheelPrintInfo();
BWMthree.printCarInfo = bwmThreePrintCarInfo;
BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year);
BWMthree.realPrintCarInfo();
Car *AodiA6 = new Car();
AodiA6->color = "黑色";
AodiA6->brand = "奥迪";
AodiA6->type = "A6";
AodiA6->year = 2008;
AodiA6->pwl = new Wheel();
AodiA6->pwl->year = 2024;
AodiA6->pwl->brand = "kk牌";
AodiA6->pwl->wheelPrintInfo();
AodiA6->printCarInfo = A6PrintCarInfo;
AodiA6->printCarInfo(AodiA6->color,AodiA6->brand,AodiA6->type,AodiA6->year);
AodiA6->realPrintCarInfo();
return 0;
}
5. 权限修饰
5.1 基本介绍
• C++中的访问权限主要分为三种:public,private和protected。这些权限决定了类成员(包括数据成员和成员函数)的可访问性。以下是一个总结表格,说明了在不同情况下这些权限如何应用:
| 访问权限 | 类内部 | 同一个类对象 | 子类 | 类外部 |
|
public
|
✔️ 可访问
|
✔️ 可访问
|
✔️ 可访问
|
✔️ 可访问
|
|
private
|
✔️ 可访问
|
❌ 不可访问
|
❌ 不可访问
|
❌ 不可访问
|
|
protected
|
✔️ 可访问
|
❌ 不可访问
|
✔️ 可访问 |
❌ 不可访问
|
• 使用权限(如public,private和protected)在C++中是一种关键的封装手段,它们旨在控制对类成员的访问。下面是一个表格,总结了使用权限的主要好处和潜在缺点:
| 好处/缺点 | 描述 |
| 好处 | |
|
封装性
|
通过隐藏类的内部实现(私有和受保护成员),提高了代码的安全性和健壮性。
|
|
接口与实现的分离
|
公开接口(公开成员)与私有实现分离,有助于用户仅关注于如何使用类而不是如何实现。
|
|
易于维护
|
修改类的内部实现不会影响使用该类的代码,从而降低了维护成本。
|
|
控制读写访问
|
通过设置访问权限,可以精确控制类成员的读写访问。 |
|
继承的灵活性
|
protected成员在派生类中是可访问的,使得继承更加灵活
|
| 缺点 | |
|
增加复杂性
|
过度使用或不当使用权限可能导致代码结构复杂,难以理解。
|
|
测试难度
|
私有成员的测试比公共成员更困难,因为它们不能从类的外部访问。
|
|
灵活性降低
|
过于严格的封装可能限制了某些有效的用法,降低了灵活性。
|
|
可能导致紧耦合
|
过多依赖friend类或函数可能导致类之间的耦合过紧。
|
6. 引用
• 引用是变量的一个别名,也就是说,它是某个已存在变量的另一个名字。
• 一旦把引用初始化为某个变量,就可以使用该引用名称或者变量名称来指向变量。
• 在C语言中,一个数据对应一个内存,通过由一个变量名来访问这个内存空间的数据,叫做直接访问,相对直接访问,有个间接访问的说法,叫做指针。
• 而引用相当于又给这个内存中的数据提供了一个新的变量名,
• 这个变量名功能比传统变量名更特殊,是直达地址的。
6.1 和指针的区别
• 引用很容易与指针混淆,它们之间有三个主要的不同:
• 不存在空引用。引用必须连接到一块合法的内存。
• 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
• 引用必须在创建时被初始化。指针可以在任何时间被初始化。
• 官方没有明确说明,但是引用确实不是传统意义上的独立变量,它不能“变”嘛 ,试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。因此,您可以通过原始变量名称或引用来访问变量的内容。例如:
int i = 17; int* p = &i; *p = 20;
• 可以为 i 声明引用变量,如下所示:
int& r = i;
double& s = d;
• 在这些声明中,&读作引用。因此,第一个声明可以读作 "r是一个初始化为i的整型引用",第二个声明可以读作 "s是一个初始化为d的double类型引用"。
6.2 引用作为函数参数
#include <iostream>
using namespace std;
// 函数声明
void swap(int& x, int& y);
int main ()
{
system("chcp 65001 > nul");//强制控制台用UTF-8编码
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;//100
cout << "交换前,b 的值:" << b << endl;//200
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;//200
cout << "交换后,b 的值:" << b << endl;//100
return 0;
}
// 函数定义
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
6.3 引用作为返回值
• 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。 例如:
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValue(int i){
double& ref = vals[i];
return ref;//返回一个指向ref返回值的隐式指针
}
int main()
{
system("chcp 65001 > nul");//强制控制台用UTF-8编码
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ ){
cout << vals[i] << endl;
}
setValue(2) = 37.5;
setValue(4) = 101.5;
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ ){
cout << vals[i] << endl;
}
return 0;
}
• 补充:当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
更多推荐
所有评论(0)