1 C和C++中 const 的区别

        在 C 和 C++ 中,const 变量的基本语法相同,即声明为 const 的变量不能被修改

const int a = 10;
a = 20;  // 错误:不能修改 const 变量

        在C中,const变量默认具有只读属性,但仍可以通过指针修改。

#include <stdio.h>

int main() {
    const int a = 10;
    int *p = (int*)&a;  // 通过指针修改
    *p = 20;
    printf("%d\n", a) // 输出20
    return 0;
}

        C 语言中的const并不真正阻止修改,只是对编译器的一个建议,但仍然可以通过 指针 修改。

        C++中,const 变量是真正的常来那个,必须在定义时初始化,不能被修改,也不能通过指针修改。

#include <iostream>
using namespace std;

int main() {
    const int a = 10;
    int* p = (int*)&a;
    *p = 20; 
    cout << *p << endl;
    cout << a << endl;
    return 0;
}

        上述代码通过指针修改const 变量并未修改成功,输出结果如下:

2 内联函数

        内联函数(Inline Function)是C++中一种优化手段,主要用来提高程序的执行效率。内联函数的本质就是在编译阶段将函数调用的代码直接嵌入到调用该函数的位置,而不是通过正常的函数调用机制来跳转执行。这样可以避免函数调用的开销,尤其是对于小型函数。他其实其实和宏函数类似。

介绍

        我们可以通过关键字inline 来定义内联函数。

inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = add(5, 3);  // 调用内联函数
    return 0;
}

        在编译时,当编译器遇到内联函数的调用时,它会尝试将该调用直接替换为内联函数的代码,而不是跳转到函数的实现部分。这种替换通常会发生在编译阶段,而不是运行时。

//在编译过程中,编译器会将add(5,3)直接替换为 5+3 
int x = add(5, 3); ====> int x = 5 + 3;

        这样,程序运行时不再需要跳转到函数的执行位置,而是直接执行计算,从而节省了时间和空间。

内联函数和宏函数的区别

1)类型检查

        内联函数是由编译器处理的,具有完整的类型检查。编译器会确保传递给内联函数的参数类型与函数定义时的类型匹配。

宏函数在与处理阶段进行文本替换,没有类型检查,因此容易出现类型不匹配或意外的行为。

2)参数求值

        内联函数在调用时传递的参数会被正常求值一次。

        宏函数是通过文本替换实现的,传递给宏的每个参数都会被替换,如果函数中包含表达式,可能会导致参数被多次求值。

#define SQUARE(x) ((x) * (x))

int main() {
    int a = 5;
    int b = SQUARE(a + 1);  // 结果是 (a + 1) * (a + 1),但 a + 1 被求值两次
    return 0;
}

3)调试支持

        内联函数在调试时可以被追踪,像普通函数一样,在内联函数内设置断点。

宏函数调试时宏展开的内容无法直接被调试器识别。宏函数展开的代码不可见,程序员无法直接在宏定义的地方设置断点,调试时不清楚发生了什么。

4)作用域

        内联函数具有正常的作用域规则。它可以有不同的作用域,且可以访问成员变量和类成员。

宏的作用域是全局的。宏在定义后,可以在整个文件中使用,知道文件结束或宏红心定义为止。

3 函数默认参数

        默认参数指的是在函数定义声明时,为某些参数提供默认值,使得调用函数时可以省略这些参数,而函数会使用默认值进行计算。

//语法
返回类型 函数名(参数类型 参数名= 默认值, ...);

//示例
#include <iostream>
using namespace std;

void greet(string name = "Guest") {
    cout << "Hello, " << name << "!" << endl;
}

int main() {
    greet();         // 使用默认值 "Guest"
    greet("Alice");  // 传入参数 "Alice"
    return 0;
}

//输出
/*
Hello, Guest!
Hello, Alice!
*/

规则

(1)默认参数必须从右往左依次定义

        默认参数必须从右侧参数开始提供,否则编译器会报错。例如:

// 错误:左边的参数没有默认值,而右边有
void display(int x = 10, int y); //  错误

(2)可以在函数声明或定义中提供默认参数,但不能两者同时提供

// 函数声明
void show(string msg = "Hello");

// 函数定义
void show(string msg) { 
    cout << msg << endl;
}

int main() {
    show();        // 输出 "Hello"
    show("Hi!");   // 输出 "Hi!"
    return 0;
}

4 函数重载

        函数重载是指在同一作用域内,函数名相同但参数列表不同(参数的类型、个数或顺序不同)的多个函数。编译器根据调用时的参数类型来区分和匹配合适的函数。

规则

(1)函数名必须相同

(2)参数列表必须不同(参数的类型、个数和顺序至少有一个不同)

//重载
int func1(int a) {
	return a;
}
//参数个数不同
int func1(int a, int b) {
	return a + b;
}
//参数类型不同
double func1(double a) {
	return a ;
}
//参数类型不同
void func1(int a, char b) {

}
//参数顺序不同
void func1(char a, int b) {

}

3)返回值不能作为重载的条件

int func() { return 1; }
double func() { return 2.5; } //错误,返回类型不同但参数列表相同

        上面的代码会报错,因为仅凭返回值不同无法区分重载

(4)默认参数可能会导致二义性,需要避免

void show(int a, int b = 10) {
    cout << "show(int, int) called" << endl;
}

void show(int a) {
    cout << "show(int) called" << endl;
}

int main() {
    show(5);  //  二义性错误,不知道调用 `show(int, int)` 还是 `show(int)`
    return 0;
}

意义

(1)提高代码的可读性和可维护性

        使用函数重载,开发者可以使用相同的函数名来表示相似的操作,从而让代码更衣理解和维护。

#include <iostream>
using namespace std;

// 重载的 print 函数
void print(int i) { cout << "整数: " << i << endl; }
void print(double d) { cout << "浮点数: " << d << endl; }
void print(string s) { cout << "字符串: " << s << endl; }

int main() {
    print(10);       // 调用 print(int)
    print(3.14);     // 调用 print(double)
    print("Hello");  // 调用 print(string)
    return 0;
}

        如果不用重载,而使用不同的函数名(如 printIntprintDoubleprintString),代码会变得臃肿,降低可读性

(2)方便拓展,提高代码灵活性

        函数重载允许程序员在不修改原有代码的情况下,添加新的功能。例如:我们需要添加打印字符的函数,可以在上面的代码中直接添加,不需要修改原有代码。

void process(char x) { cout << "处理字符: " << x << endl; }

5 面相过程 & 面相对象

5.1 面相过程 (PP)

        面向过程是一种基于函数或过程的编程方式,强调程序的执行过程。面向过程的程序通常是由一系列的函数过程子程序构成,通过这些函数来操作数据。它通常遵循“先定义数据结构,再定义函数来处理数据”的思路。

        假设你要做一道菜,整个做菜过程就像是一个面向过程的程序。

  1. 目标:最终做出一道菜。

  2. 步骤:做菜的过程是顺序执行的,包括一系列独立的步骤。

    • 准备食材(切菜、洗菜、剁肉等)。

    • 按照食谱执行一系列步骤(炒菜、煮汤等)。

    • 最后,完成的菜肴就是结果。

在这个例子中:

  • 每个步骤(如“切菜”或“炒菜”)就像一个函数,每个函数都是单独处理一些特定的工作。

  • 各个步骤是独立的,没有物品(如食材)和步骤之间的联系,它们是分开的。

        这种方式就像面向过程编程,其中步骤(函数)是顺序执行的,数据(食材)和操作(步骤)是分开的。每个步骤都是一个独立的动作,通过按顺序执行这些步骤,最终完成任务。

5.2 面相对象(OOP)

        面向对象编程是一种通过封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)来组织代码的编程方式。在OOP中,程序由多个对象组成,每个对象都是数据和操作这些数据的函数(方法)的封装。

        在面向对象的编程中,事物被视为对象,而对象具有属性(数据)和方法(行为)。一个好的现实生活类比是“组织一个团队”,这个团队中的每个人(对象)都有自己的职责(方法)和属性。

  1. 目标:完成一项复杂的任务。

  2. 对象:团队中的每个人(例如,经理、设计师、程序员等)是对象,每个人有不同的职责(方法)。

    • 经理(对象)负责指导、管理团队,拥有方法如“分配任务”。

    • 设计师(对象)负责设计,拥有方法如“绘制图纸”。

    • 程序员(对象)负责编程,拥有方法如“编写代码”。

        这些对象不仅有自己的行为(方法),还有属性(如职称、经验等)。不同的对象之间通过消息传递方法调用来协作,完成一个复杂的任务。

        例如,团队中的每个成员在执行任务时会相互协调,而不同角色之间的互动(如经理指挥、程序员编程、设计师设计)就像对象之间的交互。你可以通过实例化不同的对象来执行不同的行为(例如,你可以通过创建多个“程序员”对象来扩展团队,增加任务的处理能力)。

5.3 面相对象三大特点

5.3.1 封装

        把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作(还具备访问权限),对不可信的进行信息隐藏。 类将成员变量和成员函数封装在类的内部,根据需要设置访问权限,通过成员函数管理内部状态。

5.3.2 继承

        继承所表达的是类之间相关的关系,这种关系使得对象可以继承另外一类对象的特征和能力。 继承的作用:避免公用代码的重复开发,减少代码和数据冗余。

5.3.3 多态

        多态性可以简单地概括为“一个接口,多种方法”,字面意思为多种形态。程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态是指允许不同类的对象对同一消息作出响应的能力,即同一个接口可以有多个不同的实现。

6 类和对象

        在面向对象编程(OOP)中,类和对象是两个至关重要的概念。要理解它们,首先需要明确类(Class)对象(Object)的基本定义和它们之间的关系。

6.1 什么是类

        类是一个模板蓝图,它定义了某一类对象的共同特征和行为。换句话说,类是对象的定义,它决定了对象的数据结构(属性)和行为(方法)。类只是一个抽象的概念,无法直接使用或实例化,只有通过它才能创建对象。

组成部分:

        1)属性:类的属性定义了对象的特征。每个对象实例都会拥有一组这些属性。

        例如:一个car 类,可能拥有属性 color 、speed 等

        2)方法:类的方法定义了对象能够执行的操作或行为。每个对象实例都可以调用类中的方法来实现某些功能。

        例如:car类可能拥有 accelerate() 方法,让车加速。

6.2 什么是对象

        对象就是 two elephants ,也是类的实例,也就是说,类是定义对象的模板,而对象是根据类创建的具体实例。对象拥有类中定义的所有属性和方法,并且在程序中实际存在,可以进行操作。

6.3 类和对象的关系

        类是抽象的,它定义了对象应该具有什么属性和行为。

        对象是具体的,它根据类的定义在内存中创建出来,并且拥有类中定义的属性和方法。

每次创建一个对象,程序会根据类的定义分配内存并初始化对象的属性。

示例:

#include <iostream>
using namespace std;

// 定义一个Car类
class Car {
public:
    // 类的属性
    string color;
    int speed;
    
    // 类的方法
    void accelerate() {
        speed += 10;  // 每次加速,速度增加10
        cout << "The car accelerates. Current speed: " << speed << " km/h." << endl;
    }
    
    void displayInfo() {
        cout << "The car's color is " << color << " and its speed is " << speed << " km/h." << endl;
    }
};

int main() {
    // 创建两个对象,car1和car2
    Car car1;  
    car1.color = "Red";
    car1.speed = 50;
    
    Car car2;  
    car2.color = "Blue";
    car2.speed = 40;
    
    // 调用对象的方法
    car1.displayInfo();
    car1.accelerate();
    car1.displayInfo();
    
    car2.displayInfo();
    car2.accelerate();
    car2.displayInfo();
    
    return 0;
}

6.4 类(class)和结构体(struct)区别

        默认访问权限不同

        class:默认情况下,类的成员是 私有private)的,只有类的成员函数或友元函数能够访问这些私有成员。

        struct:默认情况下,结构体的成员(属性和方法)是 公有public)的,意味着可以直接在结构体外部访问这些成员。

 

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐