标准C++ -- day04

一、 静态成员

什么是静态成员:被static修饰的成员变量和成员函数叫做静态成员

普通成员的特点:

  • 成员变量:每个类对象中都有一份属于自己的成员变量,相互之间没有关联、独立的

  • 成员函数:隐藏着一个this指针,接收调用者地址用于区分调用者

静态成员的特点:

  • 静态成员变量:

    • 存储在data或者bss内存段中,一个类中的所有静态成员变量只有唯一一份,被所有类对象共享
    class Test
    {
        static int a;
    public:
        void show(void)
        {
            cout << a << endl;
        }
    };
    cout << sizeof(Test) << endl;// 1
    
    • 一旦成员变量被声明为静态成员变量后,通过sizeof计算类字节数时,就不统计该变量的字节数

    • 静态成员变量必须在类内加static声明,在类外单独定义、初始化,定义时无需加static

    • 静态成员变量的生命周期不再依赖于某个类对象,伴随整个程序生命周期

    • 因为静态成员变量的存在不依赖于任何类对象,所以可以无需实例化类对象,直接访问静态成员变量

      • 使用方法:

        • 对象名.成员变量/静态成员变量

        • 对象名->成员变量/静态成员变量

        • 类名::静态成员变量 (无需实例化对象,但是一般成员变量都是私有的)

  • 静态成员函数

    • 没有隐藏的this指针,所以在静态成员函数中无法直接访问普通成员变量\普通成员函数,但是手动传递对象的地址间接访问,毕竟静态成员函数还是属于类的
    • 但是可以直接访问静态成员变量\静态成员函数
    • 调用方法:
      • 对象名.成员函数( ) / 静态成员函数( )
      • 对象名->成员函数( ) / 静态成员函数( )
      • 类名::静态成员函数( ) (无需实例化对象)
  • 静态成员的作用

    • 静态成员相当于多了一层类作用域的全局变量 \ 全局函数

    • 静态成员变量适合存储所有类对象的公共属性,从而节约资源(税率\利率等)

    • 静态成员函数可以当做访问私有的静态成员变量的公开接口,一方面不破坏类的封装性,另一方面可以无需实例化对象也能调用类的静态成员,让类本身具有管理自己成员的能力

  • C和C++中的static的区别

二、 单例模式

什么是单例模式:只能实例化一个类对象

什么场景下使用单例模式:

  • 进程管理器、日志管理器

  • 网站访问计数器、应用配置程序

  • 线程池、服务器连接管理器

实现单例模式的原理:

  • 禁止在类外随意实例化对象,把构造函数\拷贝构造私有化

  • 确保类对象只有一份,在类中定义一个静态的类对象成员变量

  • 提供一个获取静态类对象成员变量的公开的接口,设计静态成员函数用于获取那唯一的一个静态类对象

饿汉模式的单例:

  • 程序运行开始时就立即实例化单例的类对象,不管后期是否用得到都会完成实例化

  • 缺点:如果后期使用不到,就浪费资源、时间

懒汉模式的单例:

  • 什么时候使用,什么时候才开始实例化单例对象

  • 优点:使用时才会创建,节约时间、资源

  • 缺点:可能会被多个线程同时实例化,有可能会创建出多个单例类对象(线程不安全)

// 饿汉模式
class Single
{
    static Single obj;
    Single(void)
    { cout << "我是构造函数" << endl; }
    Single(const Single& that)
    { cout << "我是拷贝构造" << endl; }
public:
    static Single& get_obj(void)
    {
        return obj;
    }
}
Single Single::obj;

int main(int argc,const char* argv[])
{
    Single& s = Single::get_obj();
}
// 懒汉模式
class Single
{
    static Single* obj;
    Single(void)
    { cout << "我是构造函数" << endl; }
    Single(const Single& that)
    { cout << "我是拷贝构造" << endl; }
public:
    static Single& get_obj(void)
    {
        if(NULL == obj)
        {
            obj = new Single;
        }
        return *obj;
    }
}
Single* Single::obj;
int main(int argc,const char* argv[])
{
    Single& s = Single::get_obj();
}
// 线程安全的懒汉模式

三、 运算符函数

在C++中会把运算符当做函数处理,一个表达式,其实可能调用了很多函数来完成计算,这种特性对内建类型没有用,但是对于自建类型而言,通过设计运算符函数能够进行个性化运算,以此提高代码的可读性、易用性,例如String类

string str;
str += "hehe";     strcat(str,"hehe");
str == str1;       strcmp(str,str1);

运算符函数的格式:#表示运算符,O表示运算符对象

  • 单目运算符:#O O#

    //成员函数写法:
    [] O::operator#(void)
    {
    
    }
    //返回值不确定,唯一的参数就是调用者本身
    // 全局函数写法:
    [] operator#(O& )
    {
    }
    
  • 某种运算符成员函数、全局函数只能同时实现一个,不能一起实现

  • 示例

class Test
{
public:
    int operator!(void)
    {
        cout << "我是感叹号运算符成员函数" << endl;
        return 0;
    }
}
void operator!(Test& t)// 和运算符成员函数仅能定义一个,否则会出错
{
    cout << "我是感叹号全局函数" << endl;
    return 0;
}
int main(int argc,const char* argv[])
{
    Test t;
    !t;//输出:我是感叹号运算符成员函数
}
  • 双目运算符:a # b

  • 注意:左操作数是运算符函数的发起者

    //成员函数:
    [] A::operator#(B& b)
    {
    }
    //全局函数:
    [] operator#(A& a,B& b)
    {
    }
    

示例:设计一个坐标类Point

class Point
{
    int x;
    int y;
public:
    Point(int x=0,int y=0)x(x),y(y){}
    const Point operator+(const Point& that)const // 成员函数
    {
        return Point(x+that.x,y+that.y);
    }
    friend const Point operator-(const Point& a,const Point& b);// 全局函数
    void show(void)const
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
}
const Point operator-(const Point& a,const Point& b)// 全局函数
{
    return Point(a.x-b.x,a.y-b.y);
}
int main()
{
    Point p1(1,1),p2(2,2);
    p1+p2;
}
四、 运算类的双目运算符 O是类名
// 成员函数:a + b
const O operator+(const O& b)const
{
    return O(x+b.x,y+b.y);
}
// 全局函数: a + b
const O operator+(const O& a,const O& b)
{
    return O(a.x+b.x,a.y+b.y);//会报错,x和y是私有的成员变量
}

友元:

  • 在实现类的全局运算符函数时可能会使用到类内的私有成员,此时全局函数是没有访问权限的,如果改变私有变公开会破坏类的封装性,如果提高公开的访问函数又非常麻烦,最好的方式是给该全局函数赋予独家授权,让其能够访问类内私有成员,这种行为称把该全局函数设置为友元函数

  • 方式:在类内对全局函数声明,并在声明前加 friend

五、 输入输出运算符

在C++中 <<     >> 运算符不光是按位左移、按位右移,同时还是cout该类的输出运算符 cin该类的输入运算符

输出运算符

cout << 10 << endl;
Test t;
cout << t << endl;
// 由于 << 运算符的调用者是cout对象,我们是无法在该对象的类中去设计一个输出运算符的成员函数,所以只能实现 << 运算的全局变量
ostream& operator<<(ostream& os,const O& t)
{
    return os << "(" << t.x << "," << t.y << ")";
} 
//放在Test里用friend声明

输入运算符:

istream& operator>>(istream& is,O& t)
{
    return is >> t.x >> t.y;
} 

注意:

  • 由于输出和输入是可以连续进行的,所以返回值还应该是ostream、istream引用

  • 因为无法在ostream、istream中重载运算符成员函数,所以<< >>只能重载成全局变量

  • 如果在重载全局函数中使用到自己的类中私有的成员变量,需要声明为友元

  • 输出运算符函数中,第二个参数一定要加const,而输入运算符函数不能加

六、 运算类的单目运算符

单目:++/-- ! ~ - * & sizeof

成员函数:~ !-

const O O::operator~(void)const
{
    return O(~x,~y);
}
// 注意:运算对象可以具备常属性,因此需要是常函数,其次运算的结果只是一个临时值,并且是右值,所以返回值也需要加const
// 全局函数
O operator~(const O& a)
{
    return O(~a.x,~a.y);
}
七、自变运算符函数

C++的前后自变左右值问题:

  • 能位于赋值运算符 = 左边的就是左值,反之为右值

  • 有名称、可以获取到存储地址的称为左值,反之为右值

  • C++前自变: ++num = 10;// 成功 num = 10

    • 直接修改原对象,在原对象基础上实现自变,然后将原对象的引用返回,所以操作和返回的一直是原对象,是左值
  • C++后自变:num++ = 10; // 报错

    • 先将愿对象的数据存储到临时变量中,接着在原对象基础上自变,然后把临时变量以只读方式返回,并且该临时变量执行语句结束后立即销毁了,无法访问,因此结果是右值
  • ++num++ // 后自变优先级更高,报错

  • (++num)++ // 先前自变为左值,成功

  • 注意:C语言中,无论前后自变,结果都是右值

前自变运算符:++a/--a

// 前++
// 成员函数
O& operator++(void)
{
    x++,y++;
    return *this;
}
// 全局函数
O& operator++(O& a)
{
    a.x++,a.y++;
    return a;
}

后自变运算符:a++/a--

  • 哑元:在参数列表中增加一个不使用且无形参名的int的哑元类型,唯一目的就是用来区分是前自变还是后自变
// 成员函数
O O::operator++(int)
{
    return O(x++,y++);
}
// 全局函数
O operator++(O& a,int)
{
    return O(a.x++,a.y++);
}
posted @   BigBig飞  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示