初探C++(入门)

一、类的设计
  1. 结构体 vs 类
    • C语言结构体

      • 成员变量固定大小(如char name[64]),无法动态扩展。

      • 结构体不能包含函数实现,需通过外部函数操作数据(如play_game)。

    • C++类

      • 支持成员函数(方法),直接封装在类中。

      • 成员变量和函数可设置访问权限(publicprivateprotected),默认权限为private

      • 通过公有成员函数(如setNamesetAge)间接访问私有成员,实现封装。

  2. 类的定义与权限

    • 语法结构

class ClassName {
public:
    // 公有成员(外部接口)
private:
    // 私有成员(仅类内访问)
protected:
    // 保护成员(类内及派生类访问)
};
  1. 成员变量

    用来描述一个对象的属性信息 ,与一般的变量声明相同,但需要将它存放在类的声明体中

  2. 成员函数
    • 支持内联函数、重载函数、默认参数。

    • 可在类内声明原型,类外实现(需用类名::限定)。

  3. 如果你尝试在类的外部直接访问或修改私有成员变量,编译器会报错,因为它违反了封装原则,即类的内部实现细节(如私有成员)应该被隐藏起来,只通过公共接口(如公有成员函数)与外部交互

  4. struct 与 class 的区别

    • 默认访问权限

      • struct:成员默认public

      • class:成员默认private

    • 功能等价性

      • 两者均可包含成员函数和访问控制修饰符,仅默认权限不同。


二、对象的创建与内存管理
  1. 内存区域分类

    • 栈区

      • 在函数内部定义局部对象(如Student stu;)。

      • 在函数调用结束的时候 , 栈上分配的内存会自动释放掉,这个对象也就销毁掉了

    • 静态区

      • 全局对象或static修饰的局部对象。

      • 程序结束时释放内存。

    • 堆区

      • 通过new动态分配内存(如Student *pstu = new Student;)。

      • 需手动通过delete释放内存,避免内存泄漏。

  2. 对象生命周期

    内存区域创建方式释放时机
    栈区局部变量(如Student stu函数结束自动释放
    静态区全局变量或static变量程序结束自动释放
    堆区new动态分配需手动调用delete
  1. 时间类的设计

    • 成员变量:hourminutesecond(设为private)。

    • 公有成员函数:

      • setHour()setMinute()setSecond():设置时间。

      • displayTime():输出时间。

    • 示例代码:

class Time {
private:
    int hour, minute, second;
public:
    void setHour(int h) { hour = h; }
    void setMinute(int m) { minute = m; }
    void setSecond(int s) { second = s; }
    void displayTime() {
        cout << hour << ":" << minute << ":" << second << endl;
    }
};
  1. 在不同内存区域创建对象

    • 栈区Time t;

    • 静态区

      • 全局变量:Time globalTime;

      • 局部静态变量:static Time staticTime;

    • 堆区Time *pTime = new Time;(需手动delete pTime;


三、关键概念
  • 封装:通过私有成员和公有接口隐藏实现细节。

  • 内存管理:区分栈、堆、静态区的生命周期,避免内存泄漏。

  • 访问控制:合理使用publicprivateprotected控制数据安全性。

this指针
一、this指针
  1. 定义与作用

    • this指针是一个隐式指针,指向调用成员函数的对象的首地址

    • 编译器自动添加到每个非静态成员函数的参数列表中。

    • 用途:

      • 区分同名成员变量与参数:当成员函数参数与成员变量同名时,通过this->变量名明确访问成员变量。

      • 返回对象自身引用:在链式调用中,可通过return *this返回当前对象。

  2. 示例

    void Student::setName(const string &name) {
        this->name = name;  // 使用this指针区分成员变量和参数
    }
    二、构造函数
  3. 定义与特点

    • 定义:构造函数是类的一个特殊成员函数,用于初始化对象的数据成员。

    • 特点

      • 函数名与类名相同,无返回值(包括void)。

      • 对象创建时自动调用,生命周期内仅调用一次

      • 支持重载(如默认构造函数、带参数构造函数)。

      • 不能是虚函数(虚函数依赖虚表指针,构造时虚表未初始化)。

      • 不能用const修饰(构造函数用于初始化,修改对象状态是必要的)。

  4. 构造函数的分类

    • 默认构造函数:无参数,若未手动定义,编译器自动生成。

    • 带参数构造函数:用于初始化成员变量。

    • 拷贝构造函数(未提及但相关):用于对象拷贝。

  5. 成员变量初始化方式

    • 函数体内赋值

Student::Student(const string &name, int age) {
    this->name = name;  // 先默认构造,再赋值
    this->age = age;
}

初始化列表(更高效):

Student::Student(const string &name, int age) : name(name), age(age) {}
  1. 优势:直接调用成员变量的构造函数,避免默认构造+赋值的额外开销。
  2. 注意事项

    • 若用户自定义了构造函数,编译器不再生成默认构造函数。

    • 初始化列表的成员顺序应与类中声明顺序一致,避免依赖问题。

三、实际应用示例:字符串类的设计
  1. 类定义

class String {
public:
    String(const char *str = nullptr);  // 构造函数参数为const char*
    void show();                        // 输出字符及ASCII码
private:
    char *str;                         // 动态分配的字符串
};

构造函数实现

String::String(const char *str) {
    if (str) {
        this->str = new char[strlen(str) + 1];
        strcpy(this->str, str);
    } else {
        this->str = nullptr;
    }
}

完整代码:

#include <iostream>
#include <cstring> // 需要包含头文件以使用 strlen 和 strcpy
using namespace std;

class String
{
private:
    char* str;
public:
    //构造函数实现
    String(const char* str);
    //非构造函数,需要定义类型
    void show();
    ~String();
};

String::String(const char* in_str)
{
    if(in_str!=0){
    //为防止无法进行传值
    str = new char [strlen(in_str)+1];
    strcpy(str,in_str);
    }else{
        str = nullptr;
    }
}

void String::show()
{
    //每次上来先进行判断
    if(str!=nullptr){
        //用是否到最后\0进行判断
        for(int i =0 ;str[i] != '\0';i++){
            cout << "字符: " << str[i] << ", ASCII码: " << static_cast<int>(str[i]) <<  endl;
    }
}
int main() {
    String s1("Hello World!"); // 正确:深拷贝字符串字面量
    s1.show();
    return 0;
}
  1. 关键问题

    • 字符串常量处理:字符串字面量类型为const char[],赋值给非constchar*会报错。需使用const char*参数,并在内部动态分配内存(运用到深拷贝,浅拷贝只会将地址传输)。

    • 内存管理:需在析构函数中释放动态分配的内存,避免内存泄漏。


四、总结对比
特性this指针构造函数
作用访问当前对象的成员,解决同名冲突初始化对象成员,保证对象有效状态
隐式/显式隐式存在,显式使用this->显式定义,自动调用
生命周期与成员函数调用周期一致对象创建时调用,仅一次
语法限制仅非静态成员函数可用无返回值,不能是虚函数

五、关键概念
  • 封装性:通过构造函数确保对象初始化的合法性。

  • 效率优化:优先使用初始化列表提升性能。

  • 内存安全:动态资源管理需结合构造函数和析构函数(RAII原则)。

析构函数 

一、析构函数
  1. 定义与作用

    • 析构函数是类的特殊成员函数,用于释放对象占用的资源(如动态内存)。

    • 函数名为 ~类名,无参数、无返回值,不能重载。

    • 对象生命周期结束时自动调用(如栈对象离开作用域、堆对象被 delete)。

  2. 实现示例

class String {
private:
    char *str;
public:
    String(const char *str = nullptr);
    ~String();  // 析构函数声明
};

String::~String() {
    if (this->str) {
        delete[] this->str;  // 释放动态内存
    }
}
  1. 常见错误与修复

    • 内存泄漏:未在析构函数中释放动态内存。

    • 重复释放(double free):多个对象共享同一内存(浅拷贝导致)。

    • 修复方法:在析构函数中正确释放资源,并通过深拷贝避免共享资源。


二、拷贝构造函数
  1. 定义与作用

    • 拷贝构造函数用于通过已有对象初始化新对象,函数签名为 类名(const 类名 &obj必须使用引用)

    • 默认提供浅拷贝:直接复制成员值,可能导致资源重复释放。

    • 必须自定义深拷贝:当类包含动态资源(如指针)时,需手动分配内存并复制内容。

  2. 浅拷贝 vs 深拷贝

    • 浅拷贝

String::String(const String &obj) {
    this->str = obj.str;  // 共享同一内存
}
  • 问题:两个对象的 str 指向同一内存,析构时触发 double free
  • 深拷贝

String::String(const String &obj) {
    if (obj.str) {
        int len = strlen(obj.str);
        this->str = new char[len + 1];
        strcpy(this->str, obj.str);  // 独立分配内存并复制内容
    }
}
  1. 优势:每个对象拥有独立资源,避免重复释放。
  2. 拷贝构造函数调用时机

    • 对象初始化String str2 = str1;

    • 函数传参:按值传递对象时触发拷贝构造(传递引用或指针不触发)。

    • 函数返回对象:返回局部对象时可能触发拷贝构造(受编译器优化影响)。

  3. 编译器优化与调试

    • 使用 -fno-elide-constructors 编译选项可禁用优化,观察拷贝构造函数的实际调用次数。


三、关键错误分析与修复
  1. 示例代码错误

    class Test {
    public:
        Test(const Test obj) {  // 错误:参数应为引用 `const Test &obj`
            *this = obj;        // 浅拷贝导致重复释放
        }
    };
    • 错误点

      • 拷贝构造函数参数未使用引用,导致递归调用(传值触发无限拷贝)。

      • 浅拷贝导致多个对象共享 data 指针,析构时重复释放。

    • 修复方法

      Test::Test(const Test &obj) {  // 使用引用
          data = new int[size];     // 假设 size 有效
          memcpy(data, obj.data, size * sizeof(int));  // 深拷贝
      }
  2. 析构函数实现问题

    ~Test() {
        delete data;  // 错误:未使用 `delete[]`
    }
    • 修复方法

      ~Test() {
          delete[] data;  // 正确释放数组内存
      }

最终代码:

#include <iostream>
#include <string.h>
using namespace std;
class Test
{
public:
    // 一般情况下最好使用初始化列表(更高效)
    Test(int size) : size(size)
    {
        // 直接赋值的情况下使用
        cout << "Test(int size)" << endl;
        data = new int[size];
    }
    //使用拷贝构造函数时必须传引用
    Test(const Test &obj)
    {
        cout << "Test(const Test obj)" << endl;
        // 使用浅拷贝会导致double析构
        this->size = obj.size;
        this->data = new int(obj.size);
        for (int i = 0; i < this->size; i++)
        {
            this->data[i] = obj.data[i];
        }
    }
    ~Test(void)
    {
        cout << "~Test()" << endl;
        // 数组必须要这样进行处理
        delete[] data;
    }

private:
    int size;
    int *data;
};
// 传参,用现有对象创建新的对象,返回共有3个拷贝构造函数
Test function(Test obj)
{
    Test tmp = obj;
    return tmp;
}
int main(void)
{
    Test obj1(3);
    Test obj2 = function(obj1);
    return 0;
}

四、总结对比
特性析构函数拷贝构造函数
作用释放资源初始化新对象
默认行为释放成员变量(不处理动态资源)浅拷贝(直接复制成员值)
自定义必要性需手动释放动态资源需自定义深拷贝避免共享资源
语法要求无参数,不可重载参数必须为引用 const &

五、核心原则
  • RAII(资源获取即初始化):通过构造函数获取资源,析构函数释放资源。

  • 深拷贝管理动态内存:避免浅拷贝导致的重复释放。

  • 引用传递对象:减少拷贝构造函数调用,提升性能。

#include <iostream>
#include <string.h>
using namespace std;

class student
{
private:
    string time;
    int hour;
    int min;
    int sed;

public:
    void Gethour(const int hour);
    // int Sethour();
    void Getmin(const int min);
    // int Setmin();
    void Getsed(const int sed);
    // int Setsed();
    void show_time(const string &time);
};
void student::Gethour(const int _hour)
{
    hour = _hour;
}

void student::Getmin(const int _min)
{
    min = _min;
}

void student::Getsed(const int _sed)
{
    sed = _sed;
}

void student::show_time(const string &time)
{
    cout << "hour:" << hour << "mins" << min << "sed" << sed << endl;
}

int main()
{
    student stu;
    stu.Gethour(12);
    stu.Getmin(23);
    stu.Getsed(33);
    stu.show_time("show_time");
    return 0;
}

static关键字

一、static关键字在C++类中的核心作用

static关键字在C++类中用于定义静态成员变量静态成员函数,其核心特性如下:


一、static关键字的用途

static在C++中用于修饰类的成员变量和成员函数,实现以下功能:

  1. 共享数据:静态成员变量被所有类对象共享,用于统计全局状态(如内存使用量)。

  2. 独立于对象:静态成员函数不依赖对象实例,可通过类名直接调用。

  3. 单例模式:通过私有构造函数和静态成员函数控制对象的唯一性。


二、静态成员变量
  1. 定义与初始化

    • 声明:在类内用static修饰成员变量(如static int memorySpaceSize;)。

    • 初始化:必须在类外单独初始化(如int String::memorySpaceSize = 0;)。

  2. 特性

    • 共享性:所有对象共享同一份静态变量。

    • 生命周期:与程序生命周期一致,不依赖对象存在。

    • 访问方式:通过类名(String::memorySpaceSize)或对象访问。

  3. 应用场景

    • 统计类对象的全局资源使用(如堆内存消耗)。

    • 实现单例模式中的唯一实例存储。


三、静态成员函数
  1. 定义与调用

    • 声明:在类内用static修饰成员函数(如static void showMemorySpaceSize();)。

    • 调用:通过类名直接调用(String::showMemorySpaceSize())。

  2. 特性

    • this指针:无法直接访问非静态成员变量或函数。

    • 工具性:适合实现与对象无关的功能(如打印全局状态)。

  3. 应用场景

    • 操作静态成员变量(如showMemorySpaceSize)。

    • 提供全局工具函数(如数学计算、日志记录)。


四、单例模式实现

通过static关键字实现仅允许创建一个对象的类:

class Singleton {
private:
    static Singleton* instance;  // 静态成员保存唯一实例
    Singleton() {}               // 私有构造函数

public:
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();  // 唯一实例创建
        }
        return instance;
    }
};

// 静态成员初始化
Singleton* Singleton::instance = nullptr;

关键点

  • 构造函数私有化,禁止外部直接创建对象。

  • 通过静态成员函数getInstance()控制实例的创建与访问。


五、示例代码分析与改进
  1. 内存统计示例(String类)

    • 静态变量memorySpaceSize统计所有String对象的总内存消耗。

    • 构造函数:分配内存时累加memorySpaceSize

    • 析构函数:释放内存时减少memorySpaceSize

    • 问题修复

      • 深拷贝:在拷贝构造函数中重新分配内存,避免浅拷贝导致的双重释放(double free)。

      • 内存泄漏:确保每个new[]对应delete[]

  2. 单例模式代码改进

    • 线程安全:多线程环境下需加锁(如std::mutex)。

    • 资源释放:添加deleteInstance()方法手动释放唯一实例。


六、关键对比
特性静态成员变量静态成员函数
归属类所有,对象共享类所有,与对象无关
初始化类外初始化无需特殊初始化
访问非静态成员不可直接访问不可直接访问(静态成员函数不能直接访问非静态成员变量、函数)
调用方式类名::变量名 或 对象.变量名类名::函数名() 或 对象.函数名()

七、总结
  • static的核心作用:实现数据共享、功能独立于对象、控制对象创建。

  • 内存管理:静态成员变量需谨慎管理,避免内存泄漏或数据竞争。

  • 设计模式:通过static实现单例模式,确保全局唯一性。

  • 代码规范:优先使用初始化列表、深拷贝和边界检查,提升代码健壮性。

设计一个类, 只允许这个类在外部创建一个对象 ,提示思路如下:

把 构造函数私有化 ,外面就无法创建对象了

在类内部提供一个 静态函数创建对象

#include <iostream>

using namespace std;

class Str
{
private:
    // 把构造函数私有化
    Str() {};
    // 禁止拷贝和创建新的对象
    Str(const Str &) = delete;            // 创建新的
    Str &operator=(const Str &) = delete; // 只能是定义一个operator去操作
public:
    // 静态变量函数成员获取数据
    static Str &getdata()
    {
        // 定义局部变量
        static Str str;
        return str;
    }
    void print_data(const string &msg)
    {
        cout << "Printing data: " << msg << endl;
    }
};

int main()
{
    Str &obj1 = Str::getdata();
    obj1.print_data("第一次");

    Str &obj2 = Str::getdata();
    obj2.print_data("第二次");

    cout << "obj1地址: " << &obj1 << endl;
    cout << "obj2地址: " << &obj2 << endl;
    return 0;
}

  • 这是本人的学习笔记不是获利的工具,小作者会一直写下去,希望大家能多多监督
  • 文章会每攒够两篇进行更新发布(受平台原因,也是希望能让更多的人看见)
  • 感谢各位的阅读希望我的文章会对诸君有所帮助
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值