C++入门基础—类和对象(1)

什么是类 (Class)?

是一个抽象的概念。它定义了一类事物所共有的属性 (数据)和行为(函数),类本身不占用内存空间(静态定义除外),它只是告诉编译器:如果创建一个这种类型的变量,它应该长什么样

  • 属性(Member Variables): 描述事物的特征(例如:颜色、尺寸、品牌)

  • 行为(Member Functions): 描述事物能做什么(例如:加速、刹车、鸣笛)

什么是对象 (Object)?

对象 是类的具体实例。当你根据"蓝图"(类)真正制造出一个"建筑"时,这个建筑就是一个对象,对象是存在于内存中的,你可以操作它

类的定义

类定义格式

class为定义类的关键字,name为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省 略,类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或 者成员函数

• 为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_ 或者 m 开头,注意C++中这个并不是强制的,只是一些惯例,具体看公司的要求

• C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是 struct中可以定义函数,一般情况下我们还是推荐用class定义类

定义在类面的成员函数默认为inline

代码示例

cpp 复制代码
class Date 
{
public:
    void Init(int year, int month, int day) 
    {
        if (month > 0 && month <= 12) 
        {
            _year = year;
            _month = month;
            _day = day;
        }
        else 
        {
            std::cout << "月份设置非法!" << std::endl;
        }
    }
    void Print() 
    {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;
}; 

访问限定符

C++一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限 选择性的将其接口提供给外部的用户使用

• public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访 问,protected和private是一样的,以后继承章节才能体现出他们的区别

• 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没 有访问限定符,作用域就到 }即类结束

• class定义成员没有被访问限定符修饰时默认为private,struct默认为public

• 一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public

类的声明

  • 关键字 class :它是定义"类"的起始标志。你可以把 Date 看作是一个你自己创造的新数据类型 ,就像 intfloat 一样,只不过它更复杂、功能更强

  • 大括号与分号 :类的内容包含在 {} 之中,注意 :大括号结束后的分号 ; 是语法强制要求的,它代表这个类声明语句的结束

初始化函数Init就在这里给变量初始值 ,这个初始值我们可以自己定义,也可以让编译器随机生成,但生成的这个值是一个随机值

例如

cpp 复制代码
#include <iostream>

class Date 
{
public:
    void Init(int year, int month, int day) 
    {
        if (month > 0 && month <= 12) 
        {
            _year = year;
            _month = month;
            _day = day;
        }
    }
    void Print() 
    {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;

}; 
int main() 
{
    Date d1;
    d1.Init(2026, 3, 22);
    d1.Print();
    return 0;
}

我现在用Date数据类型去定义一个d1变量,这个过程叫做**实例化,**这里我给了一个初始化的日期,那如果我没有给日期会怎么样?

cpp 复制代码
#include <iostream>

class Date 
{
public:
    void Init(int year, int month, int day) 
    {
        if (month > 0 && month <= 12) 
        {
            _year = year;
            _month = month;
            _day = day;
        }
    }
    void Print() 
    {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;

}; 
int main() 
{
    Date d1;
    d1.Print();
    return 0;
}

如果我们不给初始值,编译器就回生成一个随机指

类域

在 C++ 中,大括号 {} 通常都会开启一个作用域。当你定义一个类时:

cpp 复制代码
class Date {
    // 这里开始就是 Date 的类域
    void Init(int year, int month, int day);
    int _year;
    // 这里类域结束
};

类域决定了编译器寻找成员变量和成员函数的范围

类域的作用

名字隔离(防止冲突)

假设你的程序里有两个类:Student 类和 Teacher 类。它们可能都有一个成员变量叫 _name

  • 因为它们分别属于不同的类域,所以互不干扰

  • 编译器知道 Student::_nameTeacher::_name 是完全不同的东西

域作用域限定符 ::

当你需要在类外面定义成员函数时,类域的概念就变得至关重要了,你必须告诉编译器:"这个函数不是普通的全局函数,它是属于某个特定类域的"

对象大小

分析一下类对象中哪些成员呢?类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针,再分析一下,对象中是否有存储指针的必要呢,假如Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量 _year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了,如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这里需要再额外哆嗦一下,其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在 运行时找,就需要存储函数地址

内存对齐规则

这个以前我们再结构体讲过,这里不多赘述https://mp.csdn.net/mp_blog/creation/editor/156221708

this指针

Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init和 Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这里就要看到C++给了 一个隐含的this指针解决这里的问题

• 编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this 指针,比如Date类的Init的真实原型为, void Init(Date* const this, int year, int month, int day) • 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this- >_year = year;

• C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显 示使用this指针

cpp 复制代码
#include<iostream>
class calendar
{
public:
	//void Init(calendar* const this int year, int month, int day)
	void Init(int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	//void print(calendar* const this)
	void print()
	{
		//this->可写可不写
		std::cout <<this-> _year << "/" << this->_month << "/" <<this-> _day << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	calendar day1;
	calendar day2;
	//函数调用形参和实参不能写this指针
	//day1.Init(&day1,2005,3,12);
	day1.Init(2005, 3, 12);
	//day1.print(&d1);
	day1.print();
	//day2.Init(&day2,2005,3,12);
	day2.Init(2009, 9, 19);
	//day2.print(&d2);
	day2.print();
	return 0;
}

例1

下面程序编译运行结果是()

A、编译报错 B、运行崩溃 C、正常运行

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

很多人看到 A* p = nullptr;p->Print(); 的第一反应通常是:"这一定会导致空指针解引用,程序会崩溃!"

但实际运行结果可能会让你大吃一惊:程序不仅没有崩溃,还成功打印出了 A::Print()

为什么程序没有崩溃?

要理解这个现象,我们需要结合你之前看的那张**"对象存储方式设计"**图来分析:

  1. 成员函数的存储位置

类成员函数(如 Print)并不存储在对象实例中 ,而是存储在公共代码区

  • 当编译器编译 p->Print() 时,它其实并不需要去内存里找对象 p 的内容。

  • 编译器知道 Print 函数的地址,它会将这行代码转换成类似 A::Print(p) 的调用。

  1. this 指针的本质

当你通过对象指针调用成员函数时,指针 p 会作为隐藏参数传递给函数,也就是我们常说的 this 指针。

  • 在这个例子中,pnullptr,所以进入 Print 函数后,其内部的 this 指针也是 nullptr
  1. 是否发生了"解引用"?

空指针崩溃的根本原因是尝试访问空地址上的数据

  • Print 函数内部,代码只执行了 cout << "A::Print()" << endl;

  • 重点: 这个函数没有访问任何成员变量 (如 _a)。

  • 因为没有访问 _a(即没有执行类似 this->_a 的操作),所以程序根本没有去解引用那个空的 this 指针。既然没去碰那个空地址,自然也就不会崩溃

例2

下面程序编译运行结果是()

A、编译报错 B、运行崩溃 C、正常运行

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

首先说结论:会崩溃

为什么这次一定会崩溃?

我们要从 this 指针 的微观操作来分析。请看这个过程:

  1. 函数调用阶段 : 当你执行 p->Print() 时,编译器依然能找到 Print 函数的地址(因为它在公共代码区),它把 p(也就是 nullptr)传给了函数内部的 this 指针,到这一步,程序还没死

  2. 执行第一行cout << "A::Print()" << endl; 这行代码只涉及常量字符串输出,不依赖任何对象数据。所以你会发现,程序甚至能打印出这一行

  3. 解引用cout << _a << endl; 在编译器眼里,_a 其实是 this->_a 的缩写

  • 此时 this 的值是 0x00000000(空地址)。
  • 代码试图去 0x0 这个地址寻找变量 _a 的值。
  • 操作系统(Windows/Linux)不允许任何程序访问 0 号地址。为了保护系统安全,系统直接把你的程序"杀掉"了
相关推荐
2501_945424801 小时前
模板代码模块化设计
开发语言·c++·算法
cyforkk1 小时前
Java 并发编程教科书级范例:深入解析 computeIfAbsent 与方法引用
java·开发语言
GIS阵地2 小时前
QgsDataSourceUri解析
数据库·c++·qt·开源软件·qgis
一杯美式 no sugar2 小时前
C++入门基础
开发语言·c++
大鹏说大话2 小时前
AI 辅助编程革命:如何利用 GitHub Copilot 等工具重塑开发效率
开发语言
rit84324992 小时前
有限元法求转子临界转速的MATLAB实现
开发语言·matlab
echome8882 小时前
Python 异步编程实战:asyncio 核心概念与最佳实践
开发语言·网络·python
剑海风云2 小时前
JDK 26之安全增强
java·开发语言·安全·jdk26
左左右右左右摇晃2 小时前
Java并发——多线程
java·开发语言·jvm