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

《C++ 函数相关技术解析》

一、构造函数

1.1 定义与作用

        构造函数是一种特殊的成员函数,在创建对象时自动被调用,用于对对象进行初始化。其函数名与类名相同,没有返回值类型(包括 void 也不能有 )。

1.2 特性

  • 自动调用:每当创建类的对象时,构造函数都会被自动调用。
  • 默认构造函数:如果用户没有定义任何构造函数,编译器会自动生成一个默认构造函数。这个默认构造函数没有参数,函数体为空。但如果用户定义了至少一个构造函数,编译器就不会再自动生成默认构造函数。

1.3 调用形式

  • 无参构造函数调用Stu za; ,这种形式调用的是无参构造函数。
  • 有参构造函数调用
    • Stu za = Stu(参数); ,等价于 Stu za(参数); ,这两种形式都是调用带参数的构造函数。
    • Stu za = 参数; ,当参数类型匹配时,也会调用相应的带参构造函数。

1.4 初始化列表

        当类中有引用成员时,必须在构造函数的初始化列表中对其进行初始化,否则会导致编译错误。因为引用必须在定义时就被初始化,且之后不能再重新绑定到其他对象。

1.5 示例代码

#include <iostream>
class Stu {
private:
    int age;
public:
    // 无参构造函数
    Stu() {
        age = 0;
        std::cout << "无参构造函数被调用" << std::endl;
    }
    // 有参构造函数
    Stu(int a) {
        age = a;
        std::cout << "有参构造函数被调用,年龄为: " << age << std::endl;
    }
};
int main() {
    Stu stu1; // 调用无参构造函数
    Stu stu2(20); // 调用有参构造函数
    Stu stu3 = 22; // 调用有参构造函数
    return 0;
}

二、引用

2.1 在构造函数中的应用

        在构造函数的参数列表中使用引用类型,可以避免在函数调用时对传递的对象进行不必要的拷贝,从而提高程序的效率。特别是当传递的对象较大或者拷贝操作比较复杂时,这种方式的优势更加明显。

2.2 示例代码

#include <iostream>
class BigObject {
    // 假设这里有很多成员变量,使得对象占用较大空间
    int data[1000];
public:
    BigObject() {
        // 简单初始化
        for (int i = 0; i < 1000; ++i) {
            data[i] = i;
        }
    }
};
class Container {
private:
    BigObject obj;
public:
    // 使用引用传递参数,避免拷贝
    Container(const BigObject& bigObj) : obj(bigObj) {
        std::cout << "通过引用传递对象,避免了不必要的拷贝" << std::endl;
    }
};
int main() {
    BigObject big;
    Container container(big);
    return 0;
}

三、接口函数(set 和 get 接口 )

3.1 定义与作用

  • set 接口:专门用于给类的私有成员变量赋值,通常命名形式为 set + 成员变量名 。
  • get 接口:用于获取类的私有成员变量的值,通常命名形式为 get + 成员变量名 。

3.2 示例代码

#include <iostream>
#include <string>
class Person {
private:
    std::string name;
    int age;
public:
    // set 接口
    void setName(const std::string& n) {
        name = n;
    }
    void setAge(int a) {
        age = a;
    }
    // get 接口
    const std::string& getName() const {
        return name;
    }
    int getAge() const {
        return age;
    }
};
int main() {
    Person person;
    person.setName("Alice");
    person.setAge(25);
    std::cout << "姓名: " << person.getName() << ", 年龄: " << person.getAge() << std::endl;
    return 0;
}

四、this 指针

4.1 定义与作用

当对象调用成员函数时,this 指针会作为隐含参数传递给该函数,它指向调用该函数的对象本身。通过 this 指针,可以在成员函数中访问对象的成员变量和其他成员函数,并且可以区分成员变量和函数参数(当参数名与成员变量名相同时 )。

4.2 示例代码

#include <iostream>
class Point {
private:
    int x;
    int y;
public:
    Point(int x, int y) {
        // 使用 this 指针区分参数和成员变量
        this->x = x;
        this->y = y;
    }
    void print() {
        std::cout << "坐标: (" << x << ", " << y << ")" << std::endl;
    }
};
int main() {
    Point p(3, 4);
    p.print();
    return 0;
}

五、析构函数

5.1 定义与作用

        析构函数也是一种特殊的成员函数,与构造函数相反,它在对象生命周期结束时自动被调用,用于释放对象在生命周期内分配的资源(如动态分配的内存 ),执行一些清理工作。析构函数名是在类名前加上 ~ ,没有参数,也没有返回值类型。

5.2 特性

  • 自动调用:当对象超出作用域、使用 delete 释放对象(针对动态分配的对象 )等情况时,析构函数会自动被调用。
  • 默认析构函数:如果用户没有定义析构函数,编译器会自动生成一个默认析构函数。对于普通类,默认析构函数函数体为空;但对于包含动态分配资源或其他需要特殊清理操作的类,通常需要用户自定义析构函数。

5.3 示例代码

#include <iostream>
class Resource {
private:
    int* data;
public:
    Resource() {
        data = new int[10];
        std::cout << "构造函数被调用,分配内存" << std::endl;
    }
    ~Resource() {
        delete[] data;
        std::cout << "析构函数被调用,释放内存" << std::endl;
    }
};
int main() {
    {
        Resource res;
    } // res 对象超出作用域,析构函数被自动调用
    return 0;
}

六、拷贝构造函数

6.1 定义与作用

        拷贝构造函数是一种特殊的构造函数,用于用一个已存在的对象来初始化同类型的新对象。其函数参数是本类对象的引用(通常为 const 类名& 形式 )。

6.2 调用场景

  • 函数参数传递:当函数的参数是对象值传递时,会调用拷贝构造函数创建一个实参对象的副本传递给函数。
  • 函数返回值:当函数返回一个对象时,如果返回方式是值返回,会调用拷贝构造函数创建一个临时对象作为返回值。

6.3 传参形式(const 类名 & )原因

  • 避免拷贝:使用引用传递参数,避免了在函数调用时对传递的对象进行额外的拷贝,提高了效率。
  • 防止修改const 修饰可以防止在拷贝构造函数内部意外修改传入的对象。

6.4 浅拷贝与深拷贝

  • 浅拷贝:默认的拷贝构造函数(即编译器自动生成的 )是浅拷贝。它只是简单地复制对象的成员变量的值,对于指针类型的成员变量,只是复制指针的值(即指向的地址 ),这样会导致新对象和原对象共享同一块动态分配的内存。当对象析构时,可能会出现多次释放同一块内存等问题。
  • 深拷贝:当类中包含指针类型的成员变量,且该指针指向动态分配的资源时,通常需要自定义拷贝构造函数来实现深拷贝。深拷贝会为新对象重新分配一块与原对象相同大小的内存,并将原对象中指针指向的内容复制到新分配的内存中,使得新对象和原对象拥有各自独立的资源副本。

6.5 示例代码

#include <iostream>
#include <cstring>
class String {
private:
    char* str;
public:
    String(const char* s) {
        str = new char[strlen(s) + 1];
        strcpy(str, s);
        std::cout << "构造函数被调用" << std::endl;
    }
    // 浅拷贝构造函数(错误示范)
    String(const String& other) {
        str = other.str;
        std::cout << "浅拷贝构造函数被调用(错误方式)" << std::endl;
    }
    // 深拷贝构造函数
    String(const String& other) {
        str = new char[strlen(other.str) + 1];
        strcpy(str, other.str);
        std::cout << "深拷贝构造函数被调用" << std::endl;
    }
    ~String() {
        delete[] str;
        std::cout << "析构函数被调用" << std::endl;
    }
};
int main() {
    String s1("hello");
    String s2(s1);
    return 0;
}

七、static

7.1 静态存储区变量

  • 生命周期:静态存储区的变量在程序启动时分配内存,在程序结束时释放内存,其生命周期贯穿整个程序的运行过程。
  • 初始化:如果没有显式地对静态存储区变量进行初始化,那么对于数值类型的变量,会自动初始化为 0;对于指针类型的变量,会自动初始化为 NULL 。
  • 作用域:静态存储区变量在其定义的作用域内有效。如果是在函数内部定义的局部静态变量,它的作用域局限于该函数内部,但它的生命周期并不受函数调用结束的影响,在下次函数调用时,它的值会保持上一次函数调用结束时的值。

7.2 类的静态成员变量

  • 共享性:类的静态成员变量被该类的所有对象共享,无论创建了多少个该类的对象,静态成员变量在内存中只有一份拷贝。
  • 存储位置:静态成员变量存储在静态存储区,并不属于任何一个具体的对象实例。
  • 初始化:静态成员变量一般需要在类外进行初始化,格式为 数据类型 类名::静态成员变量名 = 初始值; 。
  • 访问方式:可以通过类名直接访问(类名::静态成员变量名 ),也可以通过对象访问(对象名.静态成员变量名 )。

7.3 类的静态成员函数

  • 无 this 指针:静态成员函数没有 this 指针,因为它并不属于任何一个具体的对象,所以不能直接访问类的非静态成员变量和非静态成员函数。
  • 访问权限:静态成员函数遵循类的访问控制规则,即如果是公有的静态成员函数,可以在类外通过类名或对象进行访问;如果是私有的静态成员函数,则只能在类内部访问。
  • 用途:常用于提供与类相关的工具函数,或者用于访问和操作类的静态成员变量。

7.4 示例代码

#include <iostream>
class Counter {
private:
    static int count;
public:
    Counter() {
        ++count;
    }
    ~Counter() {
        --count;
    }
    static int getCount() {
        return count;
    }
};
int Counter::count = 0;
int main() {
    Counter c1, c2;
    std::cout << "对象数量: " << Counter::getCount() << std::endl;
    return 0;
}

八、单例模式

8.1 饿汉模式

  • 原理:饿汉模式是在程序启动时就立即创建单例对象,由于是在单线程初始化阶段创建,所以不存在线程安全问题。
  • 实现方式:定义一个静态的单例对象,并在类外进行初始化。提供一个静态成员函数用于获取该单例对象。

8.2 懒汉模式

  • 原理:懒汉模式是在第一次使用单例对象时才进行创建,这种方式相对饿汉模式更加 “懒惰”,可以在一定程度上提高程序的启动效率。
  • 非线程安全实现:在获取单例对象的静态成员函数中,首先判断单例对象是否已经创建,如果没有创建则进行创建,然后返回单例对象。
  • 线程安全实现:为了在多线程环境下保证懒汉模式的正确性,需要使用锁机制(如 std::mutex )来防止多个线程同时创建单例对象。在获取单例对象的函数中,加锁后再进行对象是否已创建的判断和创建操作。

8.3 示例代码

#include <iostream>
#include <mutex>
// 饿汉模式
class SingletonEager {
private:
    static SingletonEager instance;
    SingletonEager() {}
public:
    static SingletonEager& getInstance() {
        return instance;
    }
};
SingletonEager SingletonEager::instance;
// 懒汉模式(非线程安全)
class SingletonLazy {
private:
    static SingletonLazy* instance;
    SingletonLazy() {}
public:
    static SingletonLazy* getInstance() {
        if (instance == nullptr) {
            instance = new SingletonLazy;
        }
        return instance;
    }
};
SingletonLazy* SingletonLazy::instance = nullptr;
// 懒汉模式(线程安全)
class SingletonThreadSafe {
private:
    static SingletonThreadSafe* instance;
    static std::mutex mutex_;
    SingletonThreadSafe() {}
public:
    static SingletonThreadSafe* getInstance() {
        std::lock_guard<std::mutex> guard(mutex_);
        if (instance == nullptr) {
            instance = new SingletonThreadSafe;
        }
        return instance;
    }
};
SingletonThreadSafe* SingletonThreadSafe::instance = nullptr;
std::mutex SingletonThreadSafe::mutex_;
int main() {
    SingletonEager& eager = SingletonEager::getInstance();
    SingletonLazy* lazy = SingletonLazy::getInstance();
    SingletonThreadSafe* threadSafe = SingletonThreadSafe::getInstance();
    return 0;
}

九、new 与 delete

9.1 new 的用法

  new 是 C++ 中用于动态内存分配的关键字,它在分配内存的同时会调用对象的构造函数进行初始化。

  • 分配单个对象
    • Stu* za = new Stu; :调用无参构造函数创建一个 Stu 类的对象,并返回指向该对象的指针。
    • Stu* za = new Stu(参数); :调用带参构造函数创建一个 Stu 类的对象,并返回指向该对象的指针。
  • 分配对象数组
    • Stu* arr = new Stu[10]; :创建一个包含 10 个 Stu 类对象的数组,调用无参构造函数初始化每个元素。
    • Stu* arr = new Stu[10](参数); :如果 Stu 类有合适的构造函数,会调用该构造函数初始化数组中的每个元素。
  • 分配基本数据类型变量和数组
    • int* a = new int; :创建一个未初始化的 int 类型变量。
    • int* a = new int(5); :创建一个初始化为 5 的 int 类型变量。
    • int* arr = new int[10]; :创建一个包含 10 个 int 类型元素的数组,元素未初始化。
    • int* arr = new int[10](); :创建一个包含 10 个 int 类型元素的数组,元素初始化为 0。

9.2 delete 的用法

delete 是与 new 对应的用于释放动态分配内存的关键字,它会调用对象的析构函数(如果是对象 )。

  • 释放单个对象Stu* za = new Stu; delete za; ,释放 za 指向的 Stu 类对象。
  • 释放对象数组Stu* arr = new Stu[10]; delete[] arr; ,释放 arr 指向的 Stu 类对象数组,会依次调用数组中每个对象的析

十、思维导图

相关文章:

  • 【Paper Tips】随记5-期刊投稿阶段说明
  • 低代码开发平台:企业数字化转型的加速器
  • Linux wifi 驱动移植适配流程详解
  • Java中如何保证高并发的数据安全
  • 高效定位 Go 应用问题:Go 可观测性功能深度解析
  • JavaScript弹出框的使用:对话框、确认框、提示框、弹窗操作
  • 智能体的核心模式和架构
  • [学术][人工智能] 001_什么是神经网络?
  • mapbox基础,使用geojson加载cluster聚合图层
  • leetcode994.腐烂的橘子
  • 使用 2 端口探头测量 40 uOhm(2000 安培)PDN 的挑战 – 需要多少 CMRR?
  • 航空记录器(黑匣子)未来发展趋势
  • Spring MVC 中<mvc:resources> 的两种配置中,`classpath:/static/`和`/static/`有什么不同
  • Python爬虫教程005:ajax的get请求豆瓣电影排行榜
  • html中img标签直接使用border-radius时会图片进行了遮挡
  • 被誉为开源RTOS的天花板ThreadX
  • 【Linux笔记】系统中的权限管理及优化
  • Linux 编程环境
  • Dify 深度集成 MCP实现灾害应急响应
  • CVP介绍
  • 教做黏土手工的网站/英文网站推广
  • 网站里的动画效果图/2023年小学生简短小新闻
  • 网站图片用什么格式/58精准推广点击器
  • 南昌网站开发/现在最好的免费的建站平台
  • 黄山旅游必去十大景点/网站搜索引擎优化工具
  • 南阳手机网站建设/seo排名优化软件价格