【c++笔记七】教你使用"const类型的对象、成员函数"和"static类型的成员函数、变量"

2015年1月28日 周三

今天长沙灰常的冷!!!要多冷有多冷!!!

不要问我为什么要把这二个知识点扯到一块来讲,因为任性!(可惜没钱)

这两个都是小知识点,但是都很有用所以为了节省篇幅扯到一块讲解吧。

——————————————华丽的分割线——————————————————

一.const类型的对象和成员函数

1.const类型的对象

定义对象时在类型前加上const,使这个对象变为const型的对象。如:const A a;
和普通const类型变量一样,我们同样需要关注const对象的初始化的问题。
除了进行const对象的初始化, 还可以通过该类提供 无参构造函数。我们先看一段错误的代码:

我们构建了一个const类型的A类型的对象a。可是没有对a进行初始化,我们看看编译器怎么说:它首先说const类型的a没有初始化,也没有一个匹配的A类型的无参构造函数A::A(){}。
所以解决错误的方法有两种,一种是改写成const A a(10);另一种就是加上一个无参构造函数:
#include <iostream>
using namespace std;
class A{
    int x;
public:
    A(int a=0):x(a){}
};
int main()
{
    const A a;
    return 0;
}
我通过给构造函数的参数设置默认值增强了兼容性(兼容无参构造函数),如果看了【c++笔记六】的童鞋应该知道这实际上一种构造函数的重载。

2.const类型的成员函数

在类的成员函数加了const修饰符就构成了const型成员函数。
但是一定要注意这个const的位置,可不是在返回值前面哦!(不然就变成了返回返回值类型的常量)
const的位置在 参数列表之后,函数实现体之前(和参数列表所处的位置一样)
就像这样:void show () const { }
那你会问,const类型的成员函数有什么用?当然有用啊,const型的成员函数 只能读取成员变量的值,不能修改。我们使用const的目的不就在于防止任何不必要的值的修改吗?(大牛说过,看一个人程序中有多少const就知道这个程序猿的水平如何)。
还需要注意的一点是: const型的成员函数和非const型的成员函数构成重载,提升兼容性。这兼容性怎么体现的?且看第三点。

3.const对象和const成员函数的关系

const对象只能调用const成员函数!
非const对象优先调用非const成员函数,没有非const成员函数则调用const函数。就是因为非const类型的对象能够调用const类型的成员函数,const成员函数和非const成员函数重载才体现了兼容性。看代码:
#include <iostream>
using namespace std;
class A{
public:
    void show(){
        cout<<"show()"<<endl;
    }
    void show() const{
        cout<<"show() const"<<endl;
    }
};
int main()
{
    A a;
    a.show();
    const A ca;
    ca.show();
    return 0;
}
对象a是非const型的,所以优先调用非const类型的show函数;因为对象ca是const类型的,所以调用的是const类型的show函数。特别地你还要注意一点,因为我们没有自定义构造函数所以系统调用缺省的无参构造函数,所以ca可以不同初始化。
如果我们把非const类型的show函数去掉会怎么样呢?
#include <iostream>
using namespace std;
class A{
public:
    void show() const{
        cout<<"show() const"<<endl;
    }
};
int main()
{
    A a;
    a.show();
    const A ca;
    ca.show();
    return 0;
}

因为没有了非const函数show,所以非const类型的对象a直接调用const类型的show函数。这样我们把两个函数浓缩成一个函数,既能被const类型的对象调用又能被非const类型的对象调用,兼容性蹭蹭蹭的上去了。
有人就会说了,既然const函数这么好,那我以后都写成const型的成员函数了。
那你有没有考虑到const函数的限制问题呢?在const函数中你可不能改变成员变量的值。而存在那种需要改变成员变量值的成员函数,全部写成const类型的显然不太合适。那有没有方法既保持这种兼容性的同时又能改变成员变量的值的方法呢?有!那就是 mutable!
看看代码你就懂了:

看,这段代码验证了我们上面的说法:const成员函数不能修改成员变量的值。我们做这样的修改:
#include <iostream>
using namespace std;
class A{
    mutable int x;
public:
    A(int x=0):x(x){}
    void show()const{
        x++;
        cout<<x<<endl;
    }
};
int main()
{
    A a(10);
    a.show();
    const A ca(100);
    ca.show();
    return 0;
}

我们仅仅在成员变量x前加上mutable修饰,就能在const类型的成员函数中修改成员变量的值啦!
满意了吧?既可以做到兼容,又可以随你干坏事。

二.static成员变量和static成员函数

说之前,我们先来思考一下为什么需要静态成员?
我们现在需要用一个Student类来表示一个班的同学。Student类除了包含学号、姓名、年龄等基本信息的成员变量外,我还希望有一个成员变量来记录整个班级的人数(有转入的可以加上,有转出的可以减去)。我们知道,Student的学号姓名等成员变量值是不同的,可是班级人数的成员变量是一直不变的且不受某一个Student的影响。就因为存在像这样不变的成员变量,我们引入了static成员变量的概念。

1.static成员变量

我们都知道static类型的变量属于 全局区,全局区在主函数运行前被创建,直到程序结束才被释放。一个类只有实例化对象后才在 栈区中创建属于自己的内存区,因为static成员变量属于 全局区,所以static完全不受某一个或多个对象的控制(因为内存完全不在一起,怎么操作)。(关于内存区域还不清楚的好好复习一下【c++笔记五】)所以对于static成员变量就完全受程序猿的控制啦!
static成员变量其实就是一种: 受类名作用域和权限控制的全局变量!(我们在下面验证这句话)
同其他类型的static变量一样,static成员变量也需要初始化!可是在哪初始化呢?定义时?初始化列表?构造函数?我们先来试试再说!

我在定义时、初始化列表中、构造函数里都对static成员变量x进行了初始化,可是没有一个成功!看看编译器怎么跟你说的:第一句提示说,c++标准禁止你在 类内对static成员变量进行初始化;第二句提示说,x是个静态成员变量,它只能在定义时被初始化!
其实第一句话已经告诉我们了,我们不能在类内对类的静态成员变量进行初始化!这话的意思就是,我们需要在类外对静态成员变量进行初始化!
那怎么样在类外对静态成员变量进行初始化呢?
格式是这样的:静态成员变量类型  类名::静态成员变量名;
如果这个静态成员变量的类型是基本的数据类型(int,char,double之类的),则初始化为 0;如果静态成员变量的类型是类类型的,则调用该类的 无参构造函数。我们还是看看代码好了:
#include <iostream>
using namespace std;
class A{
    static int x;
public:
    void show(){
        cout<<x<<endl;
    }
};
int A::x = 100;
int main()
{
    A a;
    a.show();
    return 0;
}

注意第10行,就是在初始化静态成员变量!
我们再来验证上面的那句话“static成员变量其实就是一种: 受类名作用域和权限控制的全局变量!”

这段程序有两个错误:
(1)一个发生在第13行,提示说x没有定义。哎你不是说静态变量在主函数运行前就创建了吗?为什么还说它没有调用呢?它在主函数前创建了没错了。可是你别忘了它还是一个成员变量,它是属于一个类的,当然会受类的作用域的控制啊!
(2)第二个错误发生在14行,系统提示说第十行定义的x为私有成员,所以14行不能调用类的成员变量!尽管x为静态成员变量,它还是属于A的私有成员,你是不能在类直接调用它的!
那我们应该怎样在类外调用类中的静态成员变量呢?加上作用域限定,并保证该静态成员变量为公有成员(public)!像这样:
#include <iostream>
using namespace std;
class A{
public:
    static int x;
};
int A::x = 100;
int main()
{
    cout<<A::x<<endl;
    return 0;
}



2.static成员函数

静态成员函数和静态成员变量基本类似。
静态成员函数本质上也是一种: 受类名作用域和权限控制的全局函数!
静态成员函数也可以直接使用类型名直接访问,而不像其他成员函数一样依赖任何对象。
但是,使用静态成员函数要注意一点: 函数中只能访问使用静态成员变量。我们来看代码:

我们错误的在静态成员函数中调用了非静态类型的成员变量y,编译器就提示第9行有错。你有没有想过为什么不能调用非静态成员变量呢?如果你对内存足够熟悉很容易理解,就因为静态成员函数在主函数的时候就创建出来了,而你一个类成员变量还没出生呢(没有创建在内存中)!我怎么能用一个没有出生的东西呢?
还是看看正确代码好了:
#include <iostream>
using namespace std;
class A{
    static int x;
public:
    static void show(){
        cout<<x<<endl;
    }
};
int A::x = 10;
int main()
{
    A::show();
    return 0;
}

我们可以看到13行,我们直接通过类名作用域调用了公有的静态成员函数,没有通过任何对象,这就是静态成员函数的魅力所在。

三.单例模式

因为今天的知识点不是很多,我决定在和大家一起分享一个有意思的东西—— 单例模式
什么是单例模式呢? 一个类只允许构建一个对象,这样的类类型就叫做单例模式。
是不是觉得很有意思?你可以先想一想怎么做,不会了在继续往下看。我可以提示一下需要用到的知识点: 权限控制构造函数拷贝构造函数静态成员静态成员函数引用。刚好可以把我们前面讲的知识点再复习一下。
————————————————————————————————————————————————————————————————————————
来我们先一起思考一下应该怎么做:
首先,我们应该想一想这个对象的内存存放的问题。我们是应该放在堆区?栈区?还是全局区。显然堆区和栈区的数据容易被修改和复制,不可能只会有一份。看来还是全局区靠谱了。既然是放在全局区的,那么在类中我们就应该使用 静态成员变量对吧?这个成员变量是什么呢?你觉得如果一个类的成员变量是本身类类型的变量(如:class A{ private: A a; };)行不行?肯定不行啊,如果这样的话在构建对象时就陷入了无限构建的死循环中啦。那如果这个同类类型成员变量是静态的可不可以?(如:class A{ private: static A a; };)当然可以啊,静态的东西永远只有那一份啊!显然,我们需要把这个 唯一的对象,构建为该类的静态成员变量!这样,如果不在外部定义对象,那么这个对象在内存中只有这唯一的一份。
其次,我们怎么做到不在外部定义对象呢?构建对象一般都是通过类的 构造和拷贝构造函数完成的吧,那我们是不是应该把构造和拷贝构造函数给禁止了。怎么禁止?很简单啊,把构造和拷贝构造函数的 权限控制设置为私有的,不就不能在类的外部调用了吗?
最后,我们怎么去拿这唯一的静态成员变量对象呢?调用静态成员变量当然只能通过 静态成员函数啦。你想啊,我们仅仅只留了这唯一的一份对象在全局区中,就算你拿出来用了,也只能是用这个对象的副本,实际还是在用这个对象。所以我们可以通过静态成员函数返回该对象的 引用,让我们能够使用。
大概的思路就是这样的,我们来看看代码实现:
#include <iostream>
using namespace std;
class Singleton{
    static Singleton sig;
    Singleton(){}   //构造函数设为private
    Singleton(const Singleton& a){} //拷贝构造函数设为private
public:
    static Singleton& getInstance(){    //提供接口,返回唯一对象的引用
        return sig;
    }
};
Singleton Singleton::sig;
int main()
{
    Singleton& siga = Singleton::getInstance();
    Singleton& sigb = Singleton::getInstance();
    cout<<&siga<<" "<<&sigb<<endl;
    return 0;
}

我们可以发现,siga和sigb其实就是对象sig的副本,因为他们的地址一模一样。实际上, 这个Singleton类只有一个对象,sig。
是不是很有意思,多多体会一下这个例子,加深巩固一下前面的知识。
——————————————————————写在最后——————————————————————
坑爹的CSDN,本来今天凌晨十二点的时候就写完提交了,尼玛今天一看, 给我放草稿箱里了,而且后面的三分之一全没了,真尼玛的。
算了,再写一遍加深印象。
最后总结一下,通过本文:我们学到了const对象、成员函数的使用方法,static成员变量、函数的原理和应用。最后通过单例模式的例子把我们前面所讲到的知识点稍微串起来了一下,算是复习了。






  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值