【c++面向对象编程】第2篇:类与对象(一):定义第一个类——成员变量与成员函数

目录

一、从一个日常需求开始

二、定义你的第一个类

三、访问修饰符:public、private、protected

举个例子,看看区别:

四、成员变量怎么声明?

五、成员函数:两种实现方式

方式一:类内实现(隐式内联)

方式二:类外实现(推荐)

六、一个完整的例子:时钟类

七、常见错误(新手必踩的坑)

[1. 忘了在类外实现时写类名::](#1. 忘了在类外实现时写类名::)

[2. 在private函数里访问不到外部变量(反过来也是)](#2. 在private函数里访问不到外部变量(反过来也是))

[3. 结构体和类的区别(面试爱问)](#3. 结构体和类的区别(面试爱问))

八、这一篇的收获


一、从一个日常需求开始

假设你要写一个程序管理图书。每本书有:书名、作者、价格。

用C的结构体,你可能会这样:

c

复制代码
struct Book {
    char title[100];
    char author[50];
    double price;
};

// 然后写一堆函数操作它
void printBook(struct Book* b) { ... }
void discount(struct Book* b, double rate) { ... }

没什么问题,但你要记住:哪些函数能改price?改的时候要不要校验(比如价格不能为负)?随着代码变多,这些"约定"很容易被忘记。

C++的类把数据和操作绑在一起,并且控制谁能访问什么


二、定义你的第一个类

下面是一个最简单的Book类:

cpp

复制代码
#include <iostream>
#include <string>
using namespace std;

class Book {
public:
    string title;
    string author;
    double price;
    
    void print() {
        cout << "书名:" << title << endl;
        cout << "作者:" << author << endl;
        cout << "价格:" << price << "元" << endl;
    }
    
    void setPrice(double p) {
        if (p >= 0) {
            price = p;
        } else {
            cout << "价格不能为负数!" << endl;
        }
    }
};

然后这样使用:

cpp

复制代码
int main() {
    Book b;
    b.title = "C++ Primer";
    b.author = "Lippman";
    b.setPrice(128.5);
    b.print();
    return 0;
}

你可能会说:"这不就是结构体里塞了几个函数吗?"------差不多,但多了控制权限。


三、访问修饰符:public、private、protected

上面的代码里出现了public:,它的意思是:这些东西外面可以直接访问

C++提供了三个访问修饰符:

修饰符 谁能访问 日常理解
public 任何人 公开的接口,像遥控器上的按钮
private 只有类自己的成员函数 内部秘密,用户碰不到
protected 自己 + 子类 可以传给后代,但外人不行

举个例子,看看区别:

cpp

复制代码
class BankAccount {
public:
    string owner;      // 谁都能看账户名
    void deposit(double money) {
        if (money > 0) balance += money;
    }
    double getBalance() { return balance; }
    
private:
    double balance;    // 余额不能直接碰
};

int main() {
    BankAccount acc;
    acc.owner = "张三";      // ✅ 可以,owner是public
    acc.deposit(500);        // ✅ 可以,通过函数操作
    acc.balance = 1000;      // ❌ 编译错误!balance是private
    cout << acc.getBalance(); // ✅ 只能通过函数获取
}

为什么要这样?

  • balance直接暴露在外面,万一有人写acc.balance = -10000就出问题了

  • 通过deposit()函数,我们可以加校验(只能存正数)

  • 通过getBalance()只读不写(没有提供setBalance),余额就只能看不能改

封装的核心不是"藏起来",而是"可控"


四、成员变量怎么声明?

成员变量可以像普通变量一样声明,但一般写在privateprotected区域:

cpp

复制代码
class Student {
private:
    string name;      // 字符串类型
    int age;          // 整型
    double score[5];  // 数组
    int* pData;       // 指针(后面讲动态内存时会用)
    
public:
    // 成员函数...
};

几条潜规则(不是语法强制,但建议遵守):

  1. 成员变量放在private------除非你有特殊理由

  2. 命名风格保持一致 ,很多人用下划线后缀:name_age_m_name

  3. 不要在类内给成员变量赋默认值(除非用C++11的类内初始化,后面会讲)


五、成员函数:两种实现方式

成员函数可以在类内直接写,也可以在类外写。

方式一:类内实现(隐式内联)

刚才的Book类就是类内实现:

cpp

复制代码
class Book {
public:
    void print() {
        cout << title << endl;   // 直接写在类里
    }
};

优点 :简单直观
缺点:函数体暴露在头文件里,编译依赖重(改动函数实现会重新编译所有包含头文件的代码)

方式二:类外实现(推荐)

把声明和实现分开:

cpp

复制代码
// Book.h 头文件
class Book {
public:
    void print();           // 只声明
    void setPrice(double p);
private:
    string title;
    string author;
    double price;
};

// Book.cpp 实现文件
#include "Book.h"
#include <iostream>
using namespace std;

void Book::print() {        // 注意 Book:: 表示这个函数属于Book类
    cout << "书名:" << title << endl;
    cout << "作者:" << author << endl;
    cout << "价格:" << price << "元" << endl;
}

void Book::setPrice(double p) {
    if (p >= 0) price = p;
}

Book::print()::作用域运算符 ,意思是"print这个函数是Book这个类里的"。

类外实现的优点

  • 头文件只放接口,干净整洁

  • 修改函数实现时,只编译.cpp文件,不用重新编译所有依赖

  • 多人协作时可以并行开发(一个人改.h,一个人改.cpp)

对于只有三五行的简单函数(比如getBalance()),可以直接写类内;复杂的逻辑写类外。


六、一个完整的例子:时钟类

把前面知识点串起来,我们来写一个有实际意义的Clock类。

cpp

复制代码
// Clock.h
#ifndef CLOCK_H   // 防止重复包含
#define CLOCK_H

class Clock {
public:
    void setTime(int h, int m, int s);
    void tick();      // 走一秒
    void display();   // 显示时间
    
private:
    int hour;
    int minute;
    int second;
    
    void normalize(); // 辅助函数,处理进位(只内部用,所以private)
};

#endif

cpp

复制代码
// Clock.cpp
#include "Clock.h"
#include <iostream>
#include <iomanip>
using namespace std;

void Clock::setTime(int h, int m, int s) {
    hour = h;
    minute = m;
    second = s;
    normalize();   // 万一传进来的数值超出范围
}

void Clock::tick() {
    second++;
    normalize();
}

void Clock::normalize() {
    if (second >= 60) {
        minute += second / 60;
        second %= 60;
    }
    if (minute >= 60) {
        hour += minute / 60;
        minute %= 60;
    }
    if (hour >= 24) {
        hour %= 24;
    }
}

void Clock::display() {
    cout << setfill('0');
    cout << setw(2) << hour << ":"
         << setw(2) << minute << ":"
         << setw(2) << second << endl;
}

cpp

复制代码
// main.cpp
#include "Clock.h"

int main() {
    Clock c;
    c.setTime(23, 59, 55);
    for (int i = 0; i < 10; i++) {
        c.display();
        c.tick();
    }
    return 0;
}

// 输出:
// 23:59:55
// 23:59:56
// ...
// 00:00:04

这个例子中:

  • hourminutesecondprivate------外部不能随意篡改

  • setTimetickdisplaypublic------用户通过它们操作时钟

  • normalizeprivate------外部不需要知道"进位"的具体逻辑


七、常见错误(新手必踩的坑)

1. 忘了在类外实现时写类名::

cpp

复制代码
void print() { ... }        // ❌ 这是全局函数,不是Book类的
void Book::print() { ... }  // ✅ 正确

2. 在private函数里访问不到外部变量(反过来也是)

成员函数可以访问本对象的任何成员(不管public还是private),但不是直接访问别的对象的私有成员。

3. 结构体和类的区别(面试爱问)

  • struct:成员默认是public

  • class:成员默认是private

除此之外,在C++里几乎没有区别。很多人用struct表示纯数据容器(像C语言那样)。


八、这一篇的收获

你现在应该能:

  • class定义一个类

  • 区分publicprivateprotected的访问权限

  • 在类内或类外实现成员函数

  • 理解封装的第一步:把数据藏起来,提供接口函数

💡 小作业:定义一个Rectangle类,有宽度和高度(private),提供setSize()getArea()getPerimeter()(public),并写一个main函数测试。


下一篇预告:第3篇《类与对象(二):构造函数与析构函数》------对象出生时自动执行的代码,和对象销毁时做的清理工作。你会发现,原来很多"初始化"和"收尾"的活儿,根本不用手动调用。

相关推荐
Dxy12393102161 小时前
Python Pillow库:`img.format`与`img.mode`的区别详解
开发语言·python·pillow
亿牛云爬虫专家1 小时前
深度解析:数据采集场景下的 Java 代理技术实战
java·开发语言·数据采集·动态ip·动态代理·代理配置·连接池复用
小小仙。1 小时前
IT自学第四十二天
java·开发语言
richard_yuu1 小时前
数据结构|二叉树高阶进阶-经典算法
数据结构·c++·算法
兩尛1 小时前
c++知识点5
开发语言·c++
澈2071 小时前
C++内存管理:new/delete与内存泄漏实战
开发语言·c++·内存分区
星星码️1 小时前
LeetCode刷题简单篇之反转字母
c++·算法·leetcode
其实防守也摸鱼1 小时前
VS code怎么使用 Conda 安装预编译包
开发语言·网络·c++·vscode·安全·web安全·conda
默子昂1 小时前
langchain 基本使用
开发语言·python·langchain