C++学习 day2

发布于 29 天前  16 次阅读


Day 2:C++语法再学习

1、默认参数

默认参数是 C++ 中的一种函数语法,作用是:

调用函数时,如果没有给某个参数传值,就使用提前设置好的默认值。

1.1、基本写法

#include <iostream>

void print(int count = 1)
{
    std::cout << count << std::endl;
}

int main()
{
    print(10);  // 输出 10,使用传入的参数
    print();    // 输出 1,使用默认参数

    return 0;
}

其中:

int count = 1

表示如果调用 print() 时没有传入参数,就默认使用 1


1.2、多个默认参数

函数可以有多个默认参数:

#include <iostream>
#include <string>

void showStudent(const std::string& name, int age = 18, double score = 60)
{
    std::cout << "姓名:" << name << std::endl;
    std::cout << "年龄:" << age << std::endl;
    std::cout << "分数:" << score << std::endl;
}

int main()
{
    showStudent("Tom", 20, 95.5);
    showStudent("Jack", 19);
    showStudent("Lucy");

    return 0;
}

调用效果:

showStudent("Tom", 20, 95.5)  使用传入的所有参数
showStudent("Jack", 19)       score 使用默认值 60
showStudent("Lucy")           age 使用默认值 18,score 使用默认值 60

1.3、默认参数的规则

默认参数必须从右往左连续设置。

正确写法:

void func(int a, int b = 10, int c = 20)
{
}

可以这样调用:

func(1, 2, 3);
func(1, 2);
func(1);

错误写法:

void func(int a = 10, int b, int c)
{
}

原因:

如果左边参数有默认值,那么它右边的参数也必须有默认值。

1.4、声明和定义分开时

如果函数声明和定义分开写,默认参数一般写在声明中,定义中不要重复写。

正确写法:

void print(int count = 1);

void print(int count)
{
    std::cout << count << std::endl;
}

错误写法:

void print(int count = 1);

void print(int count = 1)
{
    std::cout << count << std::endl;
}

原因:

默认参数只能指定一次。

1.5、默认参数和函数重载

默认参数和函数重载一起使用时,可能产生歧义。

例如:

void func(int a)
{
}

void func(int a, int b = 10)
{
}

调用:

func(1);

编译器可能不知道应该调用哪一个函数。

所以要注意:

默认参数和函数重载不要设计得太相似,否则容易产生二义性。

6、总结

1. 默认参数用于简化函数调用
2. 没有传参时,使用提前设置好的默认值
3. 默认参数必须从右往左连续设置
4. 声明和定义分开时,默认值只能写一次
5. 默认参数和函数重载一起使用时,要避免二义性

2、内联函数 inline

内联函数是 C++ 中用于减少函数调用开销的一种语法。

普通函数调用时,需要经历参数传递、函数跳转、返回等过程。
内联函数会建议编译器把函数代码直接展开到调用位置,从而减少函数调用开销。

基本写法:

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

调用:

int ret = add(10, 20);

编译器可能会将其优化成:

int ret = 10 + 20;

2.1、inline 的特点

1. inline 只是给编译器的建议,不是强制要求
2. 是否真正展开,由编译器决定
3. 适合短小、简单、频繁调用的函数
4. 不适合函数体很长、逻辑复杂的函数

2.2、inline 和宏函数的区别

C 语言中常用宏函数:

#define ADD(a, b) ((a) + (b))

C++ 更推荐使用内联函数:

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

区别:

宏函数:本质是文本替换,没有类型检查,容易出错
内联函数:是真正的函数,有类型检查,更安全

2.3、总结

inline 的作用:建议编译器将函数在调用处展开,减少函数调用开销。

适合:短小、简单、频繁调用的函数。
不适合:函数体复杂、代码量大的函数。

一句话理解:
内联函数就是用函数的写法,实现类似宏展开的效果,但比宏更安全。

3、extern "C" 关键字

extern "C" 是 C++ 中用于 C 和 C++ 混合编程 的关键字。

它的作用是:

告诉 C++ 编译器:这个函数按照 C 语言的方式进行链接,不要使用 C++ 的函数名修饰规则。

3.1、为什么需要 extern "C"

C++ 支持函数重载,例如:

void func(int a);
void func(double a);

为了区分同名函数,C++ 编译器会对函数名进行修饰,这个过程叫:

函数名修饰 / name mangling

而 C 语言不支持函数重载,函数名通常不会被修饰。

所以当 C++ 调用 C 语言写的函数时,可能出现:

C 语言生成的函数名 和 C++ 查找的函数名 对不上

这时就需要使用 extern "C"


3.2、基本写法

单个函数:

extern "C" int add(int a, int b);

多个函数:

extern "C" {
    int add(int a, int b);
    int sub(int a, int b);
}

3.3、常见头文件写法

如果一个 C 语言头文件既要给 C 使用,也要给 C++ 使用,通常这样写:

#ifndef ADD_H
#define ADD_H

#ifdef __cplusplus
extern "C" {
#endif

int add(int a, int b);

#ifdef __cplusplus
}
#endif

#endif

其中:

#ifdef __cplusplus

表示:

如果当前使用的是 C++ 编译器,就启用 extern "C"。

原因是 C 语言不认识 extern "C",所以必须用 #ifdef __cplusplus 进行判断。


3.4、add 库函数示例

头文件 add.h

#ifndef ADD_H
#define ADD_H

#ifdef __cplusplus
extern "C" {
#endif

int add(int a, int b);

#ifdef __cplusplus
}
#endif

#endif

源文件 add.c

#include "add.h"

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

C 程序调用:

#include <stdio.h>
#include "add.h"

int main()
{
    printf("%dn", add(10, 20));
    return 0;
}

C++ 程序调用:

#include <iostream>
#include "add.h"

int main()
{
    std::cout << add(10, 20) << std::endl;
    return 0;
}

3.5、注意事项

1. extern "C" 只影响函数名的链接方式,不会把 C++ 代码变成 C 代码
2. extern "C" 常用于 C++ 调用 C 函数或 C 库
3. extern "C" 修饰的函数不能进行函数重载
4. C 语言不认识 extern "C",所以头文件中要配合 __cplusplus 使用

错误示例:

extern "C" {
    void func(int a);
    void func(double a);  // 错误,C 链接方式下不能函数重载
}

3.6、总结

extern "C" 的作用:
让 C++ 编译器按照 C 语言的方式处理函数名,解决 C 和 C++ 混合编程时的链接问题。

常用模板:

#ifdef __cplusplus
extern "C" {
#endif

// C 函数声明

#ifdef __cplusplus
}
#endif

一句话记忆:

extern "C" 是 C++ 调用 C 代码时,用来防止函数名被 C++ 编译器改掉的。

4、类与对象

这一节开始进入 C++ 面向对象的基础。面向对象不是一上来就背概念,而是先理解一种写代码的方式:

把数据和操作数据的函数放到一起,用“对象”来管理自己的数据。

4.1、面向过程和面向对象

C++ 支持面向对象编程。面向对象和面向过程是解决问题的两种不同思想。

面向过程

面向过程关注的是解决问题的步骤,通常把一个任务拆成多个函数,然后按照顺序调用这些函数。

例如:在开发板屏幕上画一个矩形,面向过程可能会这样做:

1. 打开屏幕文件      open
2. 内存映射          mmap
3. 绘制像素点        draw_point
4. 解除映射          munmap
5. 关闭文件          close

这种写法的特点是:

数据和操作数据的函数通常是分开的。
函数负责完成某一步操作。
程序主要围绕“执行流程”展开。

面向对象

面向对象关注的是解决问题需要哪些对象。对象里面既有自己的数据,也有操作这些数据的函数。

例如:在开发板屏幕上画一个矩形,面向对象可以这样理解:

1. 定义屏幕类,描述屏幕的属性和行为
2. 创建屏幕对象
3. 定义矩形类,描述矩形的属性和行为
4. 创建矩形对象
5. 让矩形对象绘制到屏幕对象上

这种写法的特点是:

数据和函数封装在对象内部。
对象自己管理自己的数据。
程序主要围绕“对象之间的交互”展开。

4.2、面向对象的三大特性

C++ 面向对象有三个核心特征:

1. 封装 Encapsulation
2. 继承 Inheritance
3. 多态 Polymorphism

当前阶段先重点掌握封装、类和对象


4.3、什么是类 class

类是 C++ 中自定义的一种类型,用来描述同一类事物的属性和行为。

简单理解:

类 = 图纸 / 模板 / 自定义类型

比如一个矩形可以抽象成类:

矩形
属性:宽、高、位置、颜色
行为:绘制、计算面积、计算周长

一个学生也可以抽象成类:

学生
属性:学号、姓名、年龄、学校、专业
行为:学习、说话、睡觉

在 C++ 里,属性一般用成员变量表示,行为一般用成员函数表示。


4.4、什么是对象 object

对象就是根据类创建出来的具体变量。

简单理解:

类是类型,对象是这个类型创建出来的具体实例。

例如:

class Point
{
public:
    void setX(int x);
    void setY(int y);

private:
    int _x;
    int _y;
};

这里 Point 是类。

Point p1;
Point p2;

这里 p1p2 是对象。

也可以理解成:

Point 是坐标点的设计图。
p1 是一个具体坐标点对象。
p2 是另一个具体坐标点对象。

4.5、封装

封装是指:

把对象的属性和行为包装到一个类中,对外隐藏内部实现,只公开必要的接口给外部使用。

封装的常见写法是:

成员变量放 private
成员函数放 public

这样外部不能直接乱改对象内部的数据,只能通过类提供的函数访问。

封装的作用:

1. 数据保护:防止外部直接修改内部数据
2. 模块化:类的内部实现和外部使用分开
3. 灵活性:内部实现改变时,外部代码可以少改甚至不改

4.6、访问权限 public / private / protected

C++ 通过访问权限控制成员能不能被外部访问。

public      公有成员,类外部可以直接访问
private     私有成员,只能在类内部访问,类外部不能直接访问
protected   保护成员,类内部和子类可以访问,类外部不能直接访问

初学阶段最常用的是:

public  :放对外提供的函数接口
private :放不希望外部直接访问的数据

例子:

class Point
{
public:
    void setX(int x);
    int getX() const;

private:
    int _x;
};

外部可以调用:

Point p;
p.setX(100);

但是不能直接访问:

p._x = 100;  // 错误,_x 是 private

4.7、class 和 struct 的默认权限

classstruct 都可以定义类,主要区别是默认访问权限不同。

class  默认权限是 private
struct 默认权限是 public

例如:

class A
{
    int value;
};

等价于:

class A
{
private:
    int value;
};

而:

struct B
{
    int value;
};

等价于:

struct B
{
public:
    int value;
};

实际开发中,简单数据结构常用 struct,需要封装行为和数据时常用 class


4.8、类的一般形式

类的一般格式:

class 类名
{
public:
    // 对外提供的函数接口

private:
    // 内部保存的数据
};

例如:

class Point
{
public:
    void setX(int x);
    void setY(int y);
    void move(int x, int y);

    int getX() const;
    int getY() const;

private:
    int _x;
    int _y;
};

说明:

setX / setY / move 是成员函数,用来修改坐标。
getX / getY 是成员函数,用来读取坐标。
_x / _y 是成员变量,用来保存坐标数据。

4.9、类的声明和定义分离

标准 C++ 中,一个类通常分成两部分:

类的声明:写在 .hpp 头文件中
类的定义:写在 .cpp 源文件中

这样做可以让代码结构更清晰。

常见文件结构:

point.hpp    类的声明
point.cpp    类的实现
main.cpp     类的使用

4.10、point.hpp:类的声明

#ifndef POINT_HPP
#define POINT_HPP

class Point
{
public:
    // 设置 x 坐标
    void setX(int x);

    // 设置 y 坐标
    void setY(int y);

    // 同时设置 x 和 y
    void move(int x, int y);

    // 获取 x 坐标,只读成员函数
    int getX() const;

    // 获取 y 坐标,只读成员函数
    int getY() const;

private:
    int _x = 0;    // x 坐标
    int _y = 0;    // y 坐标
};

#endif

这里的头文件保护:

#ifndef POINT_HPP
#define POINT_HPP
...
#endif

作用是防止头文件被重复包含。


4.11、point.cpp:类的定义

#include "point.hpp"

void Point::setX(int x)
{
    _x = x;
}

void Point::setY(int y)
{
    _y = y;
}

void Point::move(int x, int y)
{
    _x = x;
    _y = y;
}

int Point::getX() const
{
    return _x;
}

int Point::getY() const
{
    return _y;
}

注意:

void Point::setX(int x)

这里的 Point:: 表示:

setX 这个函数属于 Point 类。

如果在类外定义成员函数,必须加上类名和作用域运算符 ::


4.12、main.cpp:类的使用

#include <iostream>
#include "point.hpp"

using namespace std;

void printPoint(const Point& pos)
{
    cout << "x = " << pos.getX() << endl;
    cout << "y = " << pos.getY() << endl;

    // pos.setX(100); // 错误,pos 是 const 引用,只能调用只读成员函数
}

int main()
{
    Point pos;

    pos.setX(100);
    pos.setY(200);

    printPoint(pos);

    pos.move(300, 400);
    printPoint(pos);

    return 0;
}

编译命令示例:

g++ main.cpp point.cpp -o app

运行:

./app

输出结果大概是:

x = 100
y = 200
x = 300
y = 400

4.13、只读成员函数 const

对于只读取成员变量、不修改对象数据的成员函数,可以在函数参数列表后面加 const

格式:

返回类型 函数名(参数列表) const;

例如:

int getX() const;
int getY() const;

定义时也必须加 const

int Point::getX() const
{
    return _x;
}

const 成员函数的含义是:

这个函数只读取对象数据,不会修改成员变量。

如果在 const 成员函数中修改成员变量,会编译报错:

int Point::getX() const
{
    _x = 100;  // 错误,const 成员函数不能修改成员变量
    return _x;
}

通过 const 引用或指向 const 的指针访问对象时,只能调用 const 成员函数。

例如:

void printPoint(const Point& pos)
{
    cout << pos.getX() << endl;  // 正确
    // pos.setX(100);            // 错误
}

4.14、set 和 get 函数

对于对象的属性,一般会提供一组函数进行访问:

set 函数:修改成员变量
get 函数:读取成员变量

例如:

void setX(int x)
{
    _x = x;
}

int getX() const
{
    return _x;
}

这样做的好处是:

1. 外部不能直接访问 private 数据
2. 后期可以在 set 函数中加入范围检查
3. 类内部实现改变时,外部调用方式不用变

比如后面可以加坐标范围限制:

void Point::setX(int x)
{
    if (x >= 0 && x <= 800)
    {
        _x = x;
    }
}

4.15、总结

1. 类是自定义类型,用来描述一类事物的属性和行为。
2. 对象是类创建出来的具体变量,也叫实例化对象。
3. 封装是把数据和函数放到类中,只暴露必要接口给外部使用。
4. public 成员可以在类外部访问,private 成员只能在类内部访问。
5. class 默认 private,struct 默认 public。
6. 类的声明一般写在 .hpp 中,类的定义一般写在 .cpp 中。
7. 类外定义成员函数时,要写 类名::函数名。
8. get 函数通常写成 const 成员函数,因为它只读取数据。

一句话记忆:

类是图纸,对象是根据图纸创建出来的具体东西;封装就是把数据藏起来,通过函数安全地访问数据。

"When faced with uncertainty, ask the spring breeze."