当前位置: 首页 > news >正文

初识C++、其中的引用、类(class)和结构体(struct)

C++ 入门:从编程思想到 Hello World

1. 面向过程 vs 面向对象

在学习 C++ 之前,先要理解它和 C 语言最大的不同:
C 是 面向过程 的语言,而 C++ 是 面向对象 的语言。

1.1 面向过程(Procedure-Oriented Programming, POP)

面向过程的核心思想是:
以函数为中心,先写出数据,然后用函数一步步操作数据。

示例(C 风格):

struct Player {int x, y;int speed;
};void Move(struct Player* p, int xa, int ya) {p->x += xa * p->speed;p->y += ya * p->speed;
}

特点:

  • Player 保存数据。

  • Move 是单独的函数,专门处理 Player

  • 数据和函数是分离的。

优点是 简单、直接,但代码规模大了以后容易难以维护。


1.2 面向对象(Object-Oriented Programming, OOP)

面向对象的核心思想是:
以对象为中心,把数据和操作数据的函数绑定在一起。

示例(C++ 风格):

class Player {
public:int x, y;int speed;void Move(int xa, int ya) {x += xa * speed;y += ya * speed;}
};

特点:

  • 数据(成员变量)和方法(成员函数)在一个类里。

  • 使用时只需要通过对象调用:

    Player player;
    player.Move(1, -1);
    

这样更清晰、可扩展。


1.3 为什么叫“面向对象”

在 OOP 里:

  • 类(Class)是模具。

  • 对象(Object)是产品。

class Dog {
public:void Bark() { std::cout << "Woof!\n"; }
};
Dog d1, d2; // 两个对象

每个对象都能叫,但属性可能不同(颜色、体重)。


1.4 OOP 三大特征

  1. 封装(Encapsulation) → 数据和方法放在对象里。
    比如:x, y 定义在 Player 里,移动操作必须通过 Move() 方法完成。

  2. 继承(Inheritance) → 子类继承父类,复用功能。
    比如:class Dog : public Animal { ... }

  3. 多态(Polymorphism) → 同样的方法,不同对象实现不同效果。
    比如:Cat.Speak() 输出“Meow”,Dog.Speak() 输出“Woof”,但我们都可以调用 Animal.Speak()


1.5 总结一句话

  • 面向过程:以函数为中心,数据和函数分离。

  • 面向对象:以对象为中心,数据和函数合为一体。

C++ 中的 main 函数与 cout 输出

学习 C++ 时,最经典的入门例子就是 Hello World 程序。虽然代码很短,但里面其实隐藏了不少知识点。今天我们来拆解一下:

#include <iostream>int main() {std ::cout << "Hello World" << std ::endl;return 0;
}

1. main 函数的返回值

在 C++ 中,main 函数是程序的入口。它和其他函数有一个小小的区别:

  • 对于一般函数:
    必须明确指定返回值类型,比如 intfloatvoid 等。

  • 对于 main 函数:
    虽然标准写法是 int main(),但如果你没有写 return 0;,编译器会默认帮你返回 0,表示程序正常结束。

也就是说:
只有 main 函数可以“偷懒”不写返回值,其他函数要么写明返回值类型,要么声明为 void


2. std::cout 与流式输出

来看这一句:

std::cout << "Hello World" << std::endl;

这里的 << 其实不是普通的“左移运算符”,而是 C++ 对运算符的重载
可以把它理解为一个函数调用,它的作用是:

  • 把右边的内容("Hello World") 推送进左边的输出流std::cout)。

  • std::cout 是 C++ 标准输出流对象,对应 终端屏幕

  • std::endl 表示 换行并刷新缓冲区,确保内容立刻显示。

因此,这一行代码就等价于:

把字符串 "Hello World" 输出到屏幕,并换行。

3. std:: 的作用

很多初学者会疑惑:为什么前面要加 std::

这是因为 coutcinstring 等标准库里的东西,全都定义在 std 命名空间(namespace)里面

  • std::cout 就是告诉编译器:我用的是 std 命名空间里的 cout

  • 如果你不写 std::,要么编译器报错,要么你得写一句:

using namespace std;

这样之后你就能直接写

 cout << "..." << endl;

而不用每次加 std:: 前缀。

不过在 小练习代码里用 using namespace std; 没问题,但是在 工程代码里推荐保留 std::
因为 using namespace std; 会把整个标准库的名字都引入,容易和你自己写的变量或函数名冲突。

C++ 中的引用(Reference)

在 C++ 里,引用(Reference)常常被称为 指针的语法糖。它能做的事情,其实都可以用指针来完成,但语法更加直观,使用起来更安全。因此在日常开发中,我们更推荐使用引用而不是指针


什么是引用?

引用并不是一个新的变量,而是一个已经存在变量的 别名。这意味着引用本身并不会单独占用内存空间。

int a = 5;
int& ref = a;   // ref 是 a 的别名
ref = 2;        // 等价于 a = 2
std::cout << a << std::endl;  // 输出 2

上面的例子中,int& ref = a; 这一句声明了一个引用。此时对 ref 的任何操作,其实就是对 a 的操作。

注意:这里的 & 并不是取地址运算符,而是 类型声明的一部分,表明这是一个引用。


为什么要使用引用?

考虑这样一个例子:

// 整型变量递增函数(无效版本)
void Increment(int x) {x++;
}int a = 5;
Increment(a);
std::cout << a << std::endl; // 输出还是 5

这里 a 并没有像我们期望的那样加 1。原因是函数参数 x值传递,即 a 的值被复制了一份传给 x,对 x 的修改不会影响 a


方法 1:指针方式

我们可以用指针传递变量的地址来修改原始值:

void Increment(int* x) {(*x)++;  // 注意括号优先级
}int a = 5;
Increment(&a);
std::cout << a << std::endl; // 输出 6

这里我们把 a 的地址传给了函数,函数内部通过解引用 *x 修改了 a


方法 2:引用方式

C++ 引入了引用,使得代码更直观:

void Increment(int& x) {x++;
}int a = 5;
Increment(a);
std::cout << a << std::endl; // 输出 6

这里 x 就是 a 的别名,直接操作 x 就等价于操作 a,不用再写解引用符号 *


引用的限制

引用一旦绑定到某个变量,就 不能再改为引用别的变量

看这个例子:

int a = 5;
int b = 8;int& ref = a;  // ref 是 a 的别名
ref = b;       // 注意:这不是让 ref 指向 b,而是把 b 的值赋给 a

结果:

  • a = 8

  • b = 8

很多初学者会以为 ref 改成引用 b 了,但其实并不是这样。引用在定义的时候就必须绑定某个变量,之后不能再改变。


如果想更改“引用指向谁”怎么办?

那就必须回到指针:

int a = 5;
int b = 8;int* ref = &a;  // ref 指向 a
ref = &b;       // 现在 ref 改成指向 b

这里 ref 不是引用,而是指针,可以随意改变它指向的对象。

C++ 中的类(class)与结构体(struct)

在 C++ 中,类(class)和结构体(struct)本质上没有引入“新的能力”,它们只是对已有功能的一种更好的组织方式。类是一种 语法糖,它的目的就是把 数据和操作这些数据的函数 放在一起,以便让代码更有结构,更容易维护。


定义一个类

来看一个例子:

class Player
{int x, y;int speed;
};

这里我们定义了一个新的类型 Player,以后就可以像定义 int 一样来定义它的对象:

Player player; // 创建一个 Player 类型的变量(对象)

player 就是 Player 的一个 对象(object),也叫做 实例(instance)。这一步操作就是所谓的 实例化(instantiation)


成员的可见性(访问权限)

如果你尝试写:

player.x = 5; 

会报错:Player::x 不可访问。这是因为在 C++ 中,类的成员默认是 private 的,只能在类的内部访问,外部无法直接访问。

我们需要明确指定可见性:

class Player
{
public:int x, y;int speed;
};

加上 public: 后,player.xplayer.y 就可以在类外访问了。
总结:

  • class 的成员默认是 private

  • struct 的成员默认是 public


给类添加方法

我们希望 Player 能移动。最简单的做法是写一个函数:

void Move(Player& player, int xa, int ya)
{player.x += xa * player.speed;player.y += ya * player.speed;
}

这样调用:

Move(player, 1, -1);

但更好的方式是直接把这个函数放进类里,这时候它就叫做 方法(method)

class Player
{
public:int x, y;int speed;void Move(int xa, int ya){x += xa * speed;y += ya * speed;}
};

调用方法:

player.Move(1, 0);

这里不需要传入 player,因为方法本身就在对象里,xyspeed 就代表这个对象的成员。


class 和 struct 的区别

唯一的区别在于 默认可见性

  • class 的成员默认是 private

  • struct 的成员默认是 public

其它方面完全一样。

因此:

  • 如果你希望所有成员都是 public,又懒得写 public:,可以用 struct

  • 如果你要写一个完整的类层次(包含继承、多态等),就用 class

这就是为什么在 C++ 中我们经常说:

  • POD(Plain Old Data) 类型(单纯的数据组合,不包含复杂逻辑),用 struct

  • 复杂逻辑的对象,用 class


示例:向量类(Vec2)

struct Vec2
{float x, y;void Add(const Vec2& other){x += other.x;y += other.y;}
};

Vec2 就是一个典型的 POD 类型,主要用来存放数据,同时加上一些简单的辅助方法。


示例:日志类(Log)

再看一个功能性更强的例子:

#include <iostream>class Log
{
public:const int LogLevelError = 0;const int LogLevelWarning = 1;const int LogLevelInfo = 2;private:int m_LogLevel = LogLevelInfo; // 默认打印所有日志public:void SetLevel(int level){m_LogLevel = level;}void Error(const char* message){if (m_LogLevel >= LogLevelError)std::cout << "[ERROR]: " << message << std::endl;}void Warn(const char* message){if (m_LogLevel >= LogLevelWarning)std::cout << "[WARNING]: " << message << std::endl;}void Info(const char* message){if (m_LogLevel >= LogLevelInfo)std::cout << "[INFO]: " << message << std::endl;}
};int main()
{Log log;log.SetLevel(log.LogLevelWarning);log.Warn("Hello World");log.Error("Hello World");log.Info("Hello World");
}

输出结果:

[WARNING]: Hello World
[ERROR]: Hello World

因为我们设置了 LogLevelWarning,所以只打印 Warning 及以上等级的日志


命名约定

注意到类中有个成员 m_LogLevel,带有 m_ 前缀。这是一种常见的编码约定,用来表示 这是一个类的成员变量,便于区分局部变量和成员变量。

http://www.dtcms.com/a/395903.html

相关文章:

  • Qt之常用控件之QWidget(四)
  • Pod生命周期
  • 【课堂笔记】复变函数-3
  • 深度学习-自然语言处理-序列模型与文本预处理
  • 【C语言】迭代与递归:两种阶乘实现方式的深度分析
  • CLIP多模态模型
  • 快手前端三面(准备一)
  • 前端-JS基础-day1
  • 【开题答辩全过程】以 J2EE在电信行业的应用研究为例,包含答辩的问题和答案
  • C++ QT Json数据的解析
  • RAG——动态护栏
  • Spring Boot 全局鉴权认证简单实现方案
  • 【靶场】webshop渗透攻击
  • 深入浅出现代GPU架构:核心类型、精度模式与选择
  • 开发避坑指南(53):git 命令行标签维护方法
  • javaEE初阶 网络编程(socket初识)
  • 基于Springboot + vue3实现的实验室研究生信息管理系统
  • TwinCat是什么
  • Linux 修炼:进程概念(下)
  • PostgreSQL 全表 count 优化实践:从 SeqScan 痛点分析到 heapam 改进与性能突破
  • 第17讲 机器学习vs神经网络
  • 1. 设计模式--工厂方法模式
  • SpringBoot常用配置
  • 【论文阅读】π0:用于通用机器人控制的视觉-语言-动作流模型
  • Spring 框架学习指南
  • Vue3 父子组件通信实战:props 与 provide/inject 方案对比及用法解析
  • el-image标签预览和VForm打包后项目上层级冲突问题
  • QML学习笔记(九)QML的全局对象
  • element里的select自定义输入的时候,不用点击下拉框选中自定义输入,而是当焦点失去的时候自动赋值输入的内容
  • 链改2.0+港促会,携手赋能 Web3引企赴港!