要玩一下 ESP32 了,得使用 C++开发程序,这玩意 20 年前接触过一点,现在得捡一捡了。
一、C++ 程序结构
#include<iostream> //引用头文件,不需要加.h 后缀int num=10;int main() {std::cout <<"我有" << num <<"个苹果" << std::endl; //支持流式输出return 0;}
引用头文件不需要写.h 后缀,引入 C 程序的头文件是可以写为cstdio 或 stdio.h。
对于C标准库头文件,C++提供了两种引入方式:
传统方式:#include <stdio.h>
C++方式:#include <cstdio>
推荐使用cstdio这种形式,因为:
所有C标准库函数都定义在std命名空间中
避免与用户自定义的全局名称冲突
符合C++标准规范
在 C++ 中,std::cout 和 std::endl 分别用于输出和换行,属于标准命名空间 std。
在流式输出中,<< 操作符可以连续使用,支持混合输出变量和字符串,编译器会自动处理类型转换。
如果不希望每次使用标准库组件时都写 std::,可以在文件开头添加 using namespace std;,但这在大型项目中可能引发命名冲突,需谨慎使用。
二 变量与数据类型
基本类型(扩展自C)
string s = "C++"; // 字符串类(需#include<string>)
类型推断(C++11)
auto x = 10; //自动推断为intauto y = 3.14; //自动推断为double
这里要注意,C++的类型推断不像jave 那种解释语言,可以动态的推断,在 C++ 中 auto 的类型是在编译期确定的,一旦推断完成,类型就固定不可更改。
注意:
auto 必须初始化,无法声明未初始化的变量
对于引用类型,需要使用 auto& 明确声明
对于常量,需要使用 const auto 或 auto const
运算符(新增)
补充说明:
new 和 delete 必须配对使用: 用 new 分配的内存必须用 delete 释放,用 new[] 分配的数组必须用 delete[] 释放,否则会导致内存泄漏或未定义行为。
现代C++建议: 为了更安全、更方便地管理动态内存,现代C++(C++11及以上)强烈推荐使用智能指针(如 std::unique_ptr, std::shared_ptr)来代替裸指针和直接的 new/delete 操作。
三 函数重载(Overloading)
int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; } // 同名函数,参数不同
函数重载的规则与原理
函数重载允许在同一作用域内创建多个同名函数,但它们的参数列表(参数的类型、数量或顺序)必须不同。仅返回值类型不同不足以构成重载。
重载解析:当调用一个重载函数时,编译器会根据传入的实参的类型和数量来匹配最合适的函数版本。
void print(int i){std::cout << "整数: " << i << std::endl;}void print(double f){std::cout << "浮点数: " << f << std::endl;}void print(const char* s){std::cout << "字符串: " << s << std::endl;}int main(){print(10); // 调用 print(int)print(3.14); // 调用 print(double)print("Hello");// 调用 print(const char*)return 0;}
注意:重载函数在编译后,编译器会为它们生成不同的内部名称(名称修饰),以便链接器能够正确区分。这也是C++支持重载而C语言不支持的原因之一。
四 函数支持传入默认参数(Default Arguments)
C++允许在函数声明中为参数指定默认值。如果调用函数时没有提供对应实参,则使用该默认值。
// 函数声明中指定默认参数void greet(std::string name, std::string prefix = "Hello") {std::cout << prefix << ", " << name << "!" << std::endl;}int main() {greet("Alice"); // 输出:Hello, Alice!greet("Bob", "Hi"); // 输出:Hi, Bob!return 0;}
规则与注意事项:
从右向左设置
-
- 默认参数必须从参数列表的
最右边
- 开始连续设置。不能为某个参数指定默认值,而它右边的参数没有默认值。
// 正确void func(int a, int b = 10, int c = 20);// 错误:b有默认值,但中间的a没有void func(int a = 10, int b, int c = 20);
注意:通常在声明中指定
默认参数通常在函数声明中指定,而非定义中。如果函数定义在调用之前,也可以在定义中指定。
五 面向对象 OOP
类的的定义
// 类与对象class People { // 类定义private:string name; // 私有成员int age;public:void setName(string n) { name = n; } // 公有方法string getName() { return name; }void setAge(int a){if(a<0){return;}age = a;} // 公有方法int getAge() { return age; } // 修正:返回类型应为int};People people = People(); // 创建对象people.setName("Alice");people.setAge(-1); // 不会生效,使用方法来操作私有变量的好处是可以在方法中做处理cout << people.getName(); // 输出 "Alice"
构造函数与析构函数
构造函数在对象创建时自动调用,用于初始化成员变量;析构函数在对象销毁时自动调用,用于清理资源。
class People {private:string name;int age;public:// 默认构造函数People() : name("未知"), age(0) {std::cout << "默认构造函数被调用" << std::endl;}// 带参数构造函数People(string n, int a) : name(n), age(a) {std::cout << "带参数构造函数被调用" << std::endl;}// 析构函数~People() {std::cout << "析构函数被调用,清理资源" << std::endl;}// 其他成员函数...};int main() {People p1; // 调用默认构造函数People p2("Bob", 25); // 调用带参数构造函数return 0; // 退出时自动调用析构函数}
六 常用容器
这里主要指动态数组和键值对
#include <vector>#include <map>vector<int> v = {1, 2, 3}; //动态数组v.push_back(4); //添加元素map<string, int> m; //键值对m["Alice"] = 90;
七 智能指针
#include <memory>unique_ptr<int> p1(new int(10)); // 自动释放内存shared_ptr<int> p2 = make_shared<int>(20);
头文件包含
#include <memory> :包含智能指针相关的标准库头文件
unique_ptr智能指针
unique_ptr<int> p1(new int(10)):创建独占所有权的智能指针
特性:同一时间只能有一个unique_ptr指向该对象
内存管理:当p1离开作用域时,自动释放分配的int内存
用途:适用于独占资源所有权的场景
shared_ptr智能指针
shared_ptr<int> p2 = make_shared<int>(20):创建共享所有权的智能指针
特性:多个shared_ptr可以共享同一个对象的所有权
创建方式:使用make_shared函数,这是更安全高效的创建方式
内存管理:通过引用计数机制,当最后一个shared_ptr被销毁时释放内存
核心优势:智能指针自动管理内存生命周期,有效防止内存泄漏
1134