C++
C语言与C++语言的区别
- C语言:是一种过程化编程语言,主要关注函数和过程。面向过程的语言; 面向函数的语言; 解决问题通过 拆分,将大的工程分解为一个个小过程;通过函数来实现;
- C++:大型,超大型软件。 执行效率高--接近C语言,开发效率比C语言要高,是一种面向对象编程语言,支持面向对象编程(OOP)的特性,如类、继承、多态等。同时,C++也支持过程化编程,因此可以看作是C语言的超集。
- 头文件,如果是C++自己的头文件,不推荐加上.h后缀,如果需要使用c语言的头文件,建议在头文件前面使用c,例如
#include<cstring>
。如果是使用自己创建的头文件与c语言一样#include"xxx.h"
。不过需要注意的是在Linux环境下最好使用g++编译器,虽然和gcc没有什么区别。
C++对C语言特性的扩展
1、 强制类型转换
markup
C语言: int a = (int)33.3;
c++ : int a = int(33.3)//int有点像函数,但仍然有点像C语言风格。
: int a = static_cast<int>(33.3);//推荐的C++风格的强制类型转换
2. const 特性
markup
C语言 const int x = 10;//用于声明一个只读变量,变量的值不能被修改。 int arr[a];错误
C++ const int x= 10;//用于定义一个常量。 int arr[a];正确。在C++中,const可以用于代替宏定义来定义常量,提供更强的类型检查和作用域控制。
使用const来替换C语言的优点:
3. 类型安全:const常量有具体的类型,而宏定义只是简单的文本替换,没有类型检查。
4. 作用域:const常量遵循C++的作用域规则,而宏定义是全局的。
5. 调试支持:const常量在调试器中可见,而宏定义在预处理阶段就被替换掉了,调试器中不可见。
--------------------------------------------------------------------------------------
#define PI 3.14159
#define AREA_OF_CIRCLE(r) (PI * (r) * (r))
int main() {
double radius = 5.0;
double area = AREA_OF_CIRCLE(radius);
return 0;
}
---------------------------------------------------------------------------------------
const double PI = 3.14159;
inline double area_of_circle(double r) {
return PI * r * r;
}
int main() {
double radius = 5.0;
double area = area_of_circle(radius);
return 0;
}
3. inline内联函数
markup
1.定义和声明:
可以在函数定义前添加inline关键字,也可以在函数声明前添加。
对于类内定义的成员函数,编译器会自动将其视为内联函数。
------------------------------------------------------------------------------
inline int add(int a, int b) {
return a + b;
}
class MyClass {
public:
// 类内定义的函数自动视为内联函数
int multiply(int a, int b) {
return a * b;
}
};
--------------------------------------------------------------------------
2.在头文件中定义inline函数:
inline函数通常放在头文件中,以便在多个源文件中共享函数定义而不会引起链接错误。
// my_functions.h
inline int square(int x) {
return x * x;
}
-----------------------------------------------------------------------
3. 避免重复定义:
inline函数必须在每个使用它的翻译单元(源文件)中定义,因此通常放在头文件中。
如果不使用inline关键字,函数定义放在头文件中会引起重复定义的错误。
总而言之:inline关键字在C++中用于建议编译器将函数内联,以减少函数调用的开销并提高性能。然而,应谨慎使用inline,避免引起代码膨胀,并信任编译器的优化能力。在头文件中定义小型、频繁调用的函数时,inline是一个有用的工具
当编译器决定将一个函数内联时,它会在编译时将函数代码插入到每个调用点。这减少了函数调用的开销(如参数传递、调用/返回栈管理等),但也会增加生成代码的大小(代码膨胀)。
4. 引用
markup
特点:
1.别名:引用是变量的别名,一旦引用被初始化,就不能改变其引用的对象。
2.必须初始化:引用在声明时必须被初始化,不能像指针那样有一个空引用。
3.语法简洁:引用的语法比指针更简洁,使用起来更方便。
------------------------------------------------------------
int a = 5;
int& ref = a;//ref是a的引用
ref = 10;//修改ref就相当于修改a
------------------------------------------------------------
void increment(int& n) {
n++;
}
int main() {
int x = 10;
increment(x);//使用引用作为函数参数可以避免拷贝,提高效率,特别是对于大对象。
std::cout << "x: " << x << std::endl; // 输出11
return 0;
}
------------------------------------------------------------------------
int& getReference(int& n) {
return n;
}
int main() {
int x = 10;
int& ref = getReference(x);//函数可以返回一个引用,这样可以直接操作函数返回的变量。
ref = 20;
std::cout << "x: " << x << std::endl; // 输出20
return 0;
}
-------------------------------------------------------------------
局部变量不能作为引用返回,但是全局变量,静态局部变量可以
5. 函数参数默认值
- 在C++中,函数参数的默认值允许你在定义函数时为参数指定一个默认值。当调用函数时,如果没有提供该参数的值,则会使用默认值。这种特性使得函数调用更加灵活和简洁,尤其是在处理可选参数时非常有用。
- 默认参数只能从右向左 依次设置,即右边的参数必须有默认值,左边的可以没有。
例如,void func(int a, int b = 10); 是有效的,而 void func(int a = 10, int b); 是无效的。 - 默认参数通常在函数的声明或定义中指定,并且不能同时在函数原型中指定和声明。
- 默认参数是C++中提高代码灵活性和可读性的重要特性之一,合理使用可以使代码更加简洁和易于维护。
6. 函数重载
- 函数重载(Function Overloading)是指在同一个作用域内,可以定义多个同名 函数,但它们的参数列表(参数的类型 、个数 或顺序 )必须不同。编译器根据调用时提供的参数类型和数量来确定应该调用哪个函数重载版本。
- 函数重载条件:1.函数名相同。2.参数列表不同(类型、个数或顺序至少有一项不同)。
cpp
#include <iostream>
// 函数重载示例
void print(int num) {
std::cout << "Integer: " << num << std::endl;
}
void print(double num) {
std::cout << "Double: " << num << std::endl;
}
void print(std::string str) {
std::cout << "String: " << str << std::endl;
}
int main() {
print(5); // 调用 print(int)
print(3.14); // 调用 print(double)
print("Hello"); // 调用 print(std::string)
return 0;
}
------------------------------------------------------------------------------
返回类型不同:函数重载不能仅通过返回类型的不同来区分,因为编译器不会根据返回类型来选择函数重载版本。
决议过程:编译器在决定调用哪个函数时,会根据实参类型最佳匹配到形参的类型。
7. 动态内存分配
- 使用new 和 delete关键字来进行内存的分配与销毁
cpp
int *p = new int;//在堆上分配一个整数,并返回一个指向该整数的指针。
//int *p:声明一个名为 p 的指针变量,该指针变量可以指向一个整数。
//= new int:将新分配的整数的地址赋值给指针 p。
---------------------------------------------
int *p = new int[2];//分配两个整数
delete []p;//删除p指针
------------------------------------------------
有点类似于c语言的指针
#include <iostream>
int main() {
int *p = new int; // p 是一个指向整数的指针
//int *p = (int*) malloc(sizeof(int)); // 分配一个整数大小的内存
*p = 42; // 通过指针 p 访问并修改堆上分配的整数的值
std::cout << "Value pointed to by p: " << *p << std::endl;
delete p; // 释放内存
return 0;
}
8. 命名空间
- 在C++中用于组织代码并避免名称冲突。通过将相关的类、函数、变量等放在同一个命名空间中,可以防止不同库或模块中定义的相同名称的实体产生冲突。
markup
定义命名空间
namespace one{
int a = 5;//
}
int main{
int b = one::a;//访问one中的成员
return 0;
}
------------------------------------
嵌套命名空间
namespace one{
int a =5;
namespace two{
int b = 3;
}
}
int main(){
int c = one::two::b;
return 0;
}
---------------------------------------------
1.使用using关键字就可以不用像std::cout这种了,如同正常在该代码区域内定义好了一样,例如using namespace std;不过需要注意using关键字的使用的范围。
2.命名空间取别名 namespace one = oneNew;
9. 面向对象的思想
- 凡是占据一定空间的事物,都可以称之为对象。这个世界是因为对象之间相互交互而运行的;一切皆对象。
- 类:它们是人对一类事物的总结,它们不存在于客观世界。
- 对象的构成需要:静态的属性(变量)和动态的行为(函数)。由于结构体只能描述属性,故采用class来定义类
- 核心思想:
封装 (Encapsulation):封装是指将数据和操作数据的方法绑定在一起,并隐藏对象的内部实现细节,仅对外提供必要的接口。这有助于保护对象的状态不被外部直接修改,从而增强代码的安全性和可维护性。
继承 (Inheritance):继承允许一个类(子类)从另一个类(父类)继承属性和方法。子类可以扩展或修改父类的行为,从而实现代码重用和逻辑扩展。
多态 (Polymorphism):多态性允许同一个操作在不同的对象上表现出不同的行为。多态通常通过方法重写和接口实现来实现,使得可以编写更加灵活和可扩展的代码。
抽象(Abstraction):抽象是指将复杂的现实世界问题简化为模型,通过剥离不必要的细节,仅保留核心特征。抽象通常通过抽象类和接口来实现。 - 主要概念
类
markup
类是对象的蓝图或模板,定义了对象的属性和行为。
class Employee {
public:
// 静态的属性
int employeeNumber;
std::string name;
int age;
double salary;
std::string department;
// 动态的方法
void displayInfo() {
std::cout << "Employee Number: " << employeeNumber << std::endl;
std::cout << "Name: " << name << std::endl;//类成员函数可以直接访问类的属性
std::cout << "Age: " << age << std::endl;
std::cout << "Salary: " << salary << std::endl;
std::cout << "Department: " << department << std::endl;
}
};
类的函数,可以写在类外(推荐)和类内,但是类中要声明该函数,如同函数声明一样
class cat{
public:
void run();//类中声明该函数
};
void cat::run()
{
code//类外实现该函数
}
对象
markup
对象是类的实例,通过类创建具体的实体
int main() {
Employee emp1;
emp1.employeeNumber = 1;
emp1.name = "John Doe";
emp1.age = 30;
emp1.salary = 50000.0;
emp1.department = "Engineering";
emp1.displayInfo();
return 0;
}
继承
markup
通过继承,一个类可以继承另一个类的属性和方法
class Manager : public Employee {//manger继承employee公共的属性和方法
public:
std::string team;
void displayInfo() {
Employee::displayInfo();
std::cout << "Team: " << team << std::endl;
}
};
多态
markup
通过多态,子类可以重写父类的方法,并且可以通过父类指针调用子类的方法。
#include <iostream>
// 定义 Employee 类
class Employee {
public:
// 虚函数 displayInfo,允许子类重写
virtual void displayInfo() {
std::cout << "Employee Info" << std::endl;
}
};
// 定义 Manager 类,继承自 Employee 类
class Manager : public Employee {
public:
// 重写 Employee 类的 displayInfo 方法
void displayInfo() override {
std::cout << "Manager Info" << std::endl;
}
};
int main() {
// 动态分配一个 Employee 对象,并将指针存储在 emp 中
Employee* emp = new Employee();
// 动态分配一个 Manager 对象,并将指针存储在 mgr 中
Employee* mgr = new Manager();
// 调用 emp 指针所指向对象的 displayInfo 方法
// 由于 emp 指向的是一个 Employee 对象,调用的是 Employee 类的 displayInfo 方法
emp->displayInfo(); // 输出 "Employee Info"
// 调用 mgr 指针所指向对象的 displayInfo 方法
// 由于 mgr 指向的是一个 Manager 对象,调用的是 Manager 类重写的 displayInfo 方法
mgr->displayInfo(); // 输出 "Manager Info"
// 释放 emp 指针所指向的内存
delete emp;
// 释放 mgr 指针所指向的内存
delete mgr;
// 程序正常结束
return 0;
}
10. 构造函数structure
- 构造函数是一个类的特殊成员函数,当创建对象时自动调用,用于初始化对象的成员变量。
- 特点:与类同名、没有返回值(void也不可以),用于和普通函数区分、构造函数在对象产生时自动执行、构造函数可以重载(可以有多个)、不写构造函数,系统会自动创建,但是什么都不做。
- 当创建一个对象时,只会执行一个构造函数,即根据传递的参数匹配的那个构造函数。
markup
#include <iostream>
#include <string>
// 定义 Employee 类
class Employee {
public:
int employeeNumber;
std::string name;
int age;
double salary;
std::string department;
// 默认构造函数
Employee() {
employeeNumber = 0;
name = "Unknown";
age = 0;
salary = 0.0;
department = "None";
std::cout << "Default constructor called" << std::endl;
}
// 带参数的构造函数
Employee(int empNum, std::string empName, int empAge, double empSalary, std::string empDepartment)
: employeeNumber(empNum), name(empName), age(empAge), salary(empSalary), department(empDepartment) {
std::cout << "Parameterized constructor called" << std::endl;
}
// 另一个带参数的构造函数
Employee(std::string empName, int empAge)
: employeeNumber(0), name(empName), age(empAge), salary(0.0), department("None") {
std::cout << "Name and age constructor called" << std::endl;
}
// 显示信息的方法
void displayInfo() {
std::cout << "Employee Number: " << employeeNumber << std::endl;
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "Salary: " << salary << std::endl;
std::cout << "Department: " << department << std::endl;
}
};
int main() {
// 调用默认构造函数
Employee emp1;
emp1.displayInfo();
// 调用带参数的构造函数
Employee emp2(1, "John Doe", 30, 50000.0, "Engineering");
emp2.displayInfo();
// 调用另一个带参数的构造函数
Employee emp3("Jane Smith", 25);
emp3.displayInfo();
return 0;
}
-----------------------------------------------------------
#include <iostream>
class MyClass {
public:
int value;
// 默认构造函数,使用初始化列表
MyClass() : value(0) {
std::cout << "Default constructor called" << std::endl;
}
// 带参数的构造函数,使用初始化列表
MyClass(int val) : value(val) {
std::cout << "Parameterized constructor called" << std::endl;
}
void display() const {
std::cout << "Value: " << value << std::endl;
}
};
int main() {
MyClass obj1; // 调用默认构造函数
obj1.display(); // 输出:Value: 0
MyClass obj2(42); // 调用带参数的构造函数
obj2.display(); // 输出:Value: 42
return 0;
}
---------------------------------------------------
初始化列表:
优点:高效,直接初始化成员变量。
语法:使用冒号 : 后跟成员变量及其初始值。
构造函数体内赋值:
缺点:先调用默认构造函数初始化成员变量,再赋值,效率较低。
语法:在构造函数体内进行赋值操作。
11. 拷贝构造函数
- 拷贝构造函数(Copy Constructor)是C++中用于创建一个新对象,该对象是通过复制一个现有对象来初始化的。
- 拷贝构造可以不写,系统自动创建默认拷贝构造,他只是完成简单的 值覆盖
- 定义格式
ClassName(const ClassName &other);
markup
#include <iostream> // 引入输入输出流库,用于打印输出
// 定义 MyClass 类
class MyClass {
public:
int value; // 成员变量,存储整数值
// 默认构造函数
MyClass() : value(0) { // 初始化列表,将 value 初始化为 0
std::cout << "Default constructor called" << std::endl; // 输出默认构造函数被调用的消息
}
// 带参数的构造函数
MyClass(int val) : value(val) { // 初始化列表,将 value 初始化为传递的参数 val
std::cout << "Parameterized constructor called" << std::endl; // 输出带参数构造函数被调用的消息
}
// 拷贝构造函数
MyClass(const MyClass &other) : value(other.value) { // 初始化列表,将 value 初始化为 other 的 value
std::cout << "Copy constructor called" << std::endl; // 输出拷贝构造函数被调用的消息
}
// 显示信息的方法
void display() const { // const 成员函数,不修改对象
std::cout << "Value: " << value << std::endl; // 输出 value 的值
}
};
int main() {
// 使用默认构造函数创建对象
MyClass obj1; // 创建 obj1,调用默认构造函数
obj1.display(); // 调用 display 方法,输出 obj1 的 value 值
// 使用带参数的构造函数创建对象
MyClass obj2(42); // 创建 obj2,调用带参数的构造函数,传递参数 42
obj2.display(); // 调用 display 方法,输出 obj2 的 value 值
// 使用拷贝构造函数创建对象
MyClass obj3(obj2); // 创建 obj3,使用 obj2 初始化,调用拷贝构造函数
obj3.display(); // 调用 display 方法,输出 obj3 的 value 值
return 0; // 返回 0,表示程序正常结束
}
12. 析构函数
- 析构函数是C++类中的一种特殊成员函数,当对象的生命周期结束时(例如对象超出作用域或显式删除对象时),析构函数会被调用。析构函数的主要用途是释放对象占用的资源,例如动态分配的内存、文件句柄或网络连接等。
- 析构函数的定义没有返回类型,也没有参数,名称与类名相同,但前面有一个波浪号 (~)。定义格式`~ClassName();
- 对象超出作用域或使用 delete 删除对象时,析构函数会被自动调用。
- 通过合理地定义和使用析构函数,可以确保对象在销毁时正确释放资源,避免资源泄漏。
- .析构不允许重载
clojure
#include <iostream>
class MyClass {
public:
int* data;
// 默认构造函数
MyClass() : data(new int(0)) {
std::cout << "Default constructor called" << std::endl;
}
// 带参数的构造函数
MyClass(int val) : data(new int(val)) {
std::cout << "Parameterized constructor called" << std::endl;
}
// 拷贝构造函数
MyClass(const MyClass& other) : data(new int(*other.data)) {
std::cout << "Copy constructor called" << std::endl;
}
// 析构函数
~MyClass() {
std::cout << "Destructor called" << std::endl;
delete data; // 释放动态分配的内存
}
void display() const {
std::cout << "Value: " << *data << std::endl;
}
};
int main() {
MyClass obj1; // 调用默认构造函数
obj1.display(); // 输出:Value: 0
MyClass obj2(42); // 调用带参数的构造函数
obj2.display(); // 输出:Value: 42
MyClass obj3(obj2); // 调用拷贝构造函数
obj3.display(); // 输出:Value: 42
// 作用域结束,obj1, obj2, obj3 的析构函数会被自动调用
return 0; // 返回 0,表示程序正常结束
}
13. 参数列表初始化
- 参数列表初始化(Parameter List Initialization)是C++中一种初始化对象的方式,它允许在创建对象时直接提供初始化参数,而不需要调用特定的构造函数。这种初始化方式通常用于构造函数和类类型的成员变量初始化。
bash
#include <iostream>
class Point {
public:
int x, y;
// 构造函数
Point(int initialX, int initialY) : x(initialX), y(initialY) {
// 构造函数体
}
};
int main() {
// 使用参数列表初始化对象
Point p1(10, 20);
Point p2(30, 40);
std::cout << "p1: (" << p1.x << ", " << p1.y << ")" << std::endl;
std::cout << "p2: (" << p2.x << ", " << p2.y << ")" << std::endl;
return 0;
}
`