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;
这里 p1 和 p2 是对象。
也可以理解成:
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 的默认权限
class 和 struct 都可以定义类,主要区别是默认访问权限不同。
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 成员函数,因为它只读取数据。
一句话记忆:
类是图纸,对象是根据图纸创建出来的具体东西;封装就是把数据藏起来,通过函数安全地访问数据。






Comments | NOTHING