C++总结三----静态成员,const修饰类成员,内联函数

非常感谢您能参观我的博客,有什么想法请留言,必回。

一 · 静态成员

我们知道全局变量能够实现数据共享,如果在多个程序文件中有多个函数,每一个函数中都可以来改变全局变量的值,但是这样做是不安全的,如不小心改错就会导致程序崩溃,因此在实际运用中很少用到全局变量。
但是有时候我们就是要用到多个对象之间实现数据共享,除了全局变量,就是我们今天要提到的静态成员

1.1 静态数据成员

class Person
{
private:
    char* _name;
    static char* _gender;    //把_gender定义为静态数据成员
    int   _age;
}

静态数据成员是一个特殊的数据成员,以static开头。如果希望个对象的数据成员的值是一样的,就可以把它定义为静态数据成员。声明为静态数据成员有个好处就是它只占用一分内存空间,而不是每个对象都为它留下一块空间。

对于静态成员函数作以下几点说明:
⑴前面说过,如果只申明类而未定义对象,则类的一般数据成员是不占空间的,只有在定义对象时,才会为对象的数据成员分配空间。但是静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间,它是在对象之外单独开辟空间,只要定义了静态成员变量,即使不定义对象,也会为其分配空间还能被引用。
⑵静态数据成员既然不会随着对象的建立而分配空间,那么自然也不会随着对象的撤销而释放。静态数据成员是在程序开始运行时被分配空间,到程序结束时才会释放空间。
⑶静态数据成员只能在类外进行初始化。

char* Person::_gender ="man" 

要注意的是不能用参数初始化表来对静态数据成员初始化:
这里写图片描述
⑷静态数据成员既可以通过对象名引用还可以通过类名引用:

class Person
{
public:
    Person(char* name, char* gender, int age)
        :_name(name)
        , _age(age)
       {}
    static char* _gender;
private:
    char* _name;    
    int  _age;
};

char* Person::_gender = "man";

int main()
{
    Person p("Peter", "man", 18);
    cout << Person::_gender << endl;
    cout << p._gender << endl;
    return 0;
}

这里写图片描述

如果静态数据成员定义为私有的,就不能在类外直接引用,而必须通过公有函数进行引用。

class Person
{
public:
    Person(char* name, char* gender, int age)
        :_name(name)
       {}
    static char* _gender;
    int PersonAge();
private:
    char* _name;    
    static int  _age;
};

char* Person::_gender = "man";

int Person::PersonAge()
{
    return _age=18;
}

⑸与全局变量不同的是,静态数据成员的作用域只限于定义该类的作用域中,在类的作用域中可以通过类名和域运算符“::”来进行引用

2 静态成员函数
和静态数据成员一样,成员函数也可以是静态的,在类里声明的函数名前加上“static”就成了静态成员函数,它是类的一部分而不是对象的一部分。如果在类为引用静态成员函数需要用类名和域运算符“::”。如:

Person::PersonAge(); 

实际上也可以写成:

扫描二维码关注公众号,回复: 2566291 查看本文章
p.PersonAge();

但这并不能说这个函数属于对象p的,而是只能说用了p的类型而已。
当调用一个对象的成员函数数时(非静态成员函数),系统会把该对象的起始地址赋给成员函数的this指针。而静态成员数不属于对象的成员函数,与任何对象无关,因此静态成员函数没有this指针。既然没有指向某一对象,就无法对一个对象里的非静态成员进行默认访问(即在引用数据成员时不指定对象名)—-面试题

静态成员函数可以直接引用本类中的静态成员,我们给个例子再来说明一下:

class Box
{
public:
    Box(int length,int width,int height)
        : _width(width)
        , _height(height)
    {}
    static void volume();     //静态成员函数
private:
    static int _length;
    int _width;
    int _height;
};
void Box::volume()
{
    //cout<<_width<<endl;       //引用非静态成员函数,不合法,出错
    cout << _length << endl;    //在静态成员函数中打印静态成员变量
}
int main()
{
    Box b(11, 12, 13);
    return 0;
}

这里写图片描述
但是,并不是说绝对不能引用本类中的非静态成员,只是不能进行默认访问,应为无法知道应该去找哪一个对象。如果一定要引用非静态成员变量,应该在加上对象名和成员运算符“.
如:

cout<<b._width<<endl;

这里说明一点:公有的成员函数既可以引用本对象中的非静态数据成员,也可以引用类中的静态数据成员。

二 const修饰类成员

我们知道C++中对数据的安全性做了很大的保证,引入了private和protect两个关键字,但有些数据却往往是共享的,如果用户的误操作改了这些数据的状况,后果往往是不可预料的,伟大的科学家为解决这一问题,想出了既能使这些数据在一定范围内共享,又可以保证数据的安全,那就是const常量。
1 常对象

Date const d1(2018,7,26);     //定义d1为常对象,常对象必须要有初值

在d1的生命周期内,对象d1的所有数据都不能被更改,凡是希望数据成员不被改变的对象都可以声明为const。
对常对象做两点说明:
⑴如果一个对象被声明为常对象,则通过该对象只能调用它的常成员函数,而不能调用该对象的其他普通成员函数(除了系统自动调用的析构函数和构造函数之外)。常成员函数是常对象的唯一接口

Date const d1(2018,7,26); 
d1.get_date();   //假设该类中有get_date这个普通函数,非法 

这是为了防止普通成员函数会修改常对象中数据成员的值。,那如果要引用常对象中的数据成员,到底要怎么做呢? 很简单 只需要将该成员函数声明为const即可。

void get_date()const;    //将函数声明为const类型的函数,注意const在函数名和()后面

⑵常成员函数可以访问常对象中的数据成员,但不允许修改常对象中数据称成员的值。 如果非要对类中的某个数据进行修改,该数据成员定义声明前 必须加 mutable**关键字**

2 常数据成员
在用const关键字声明常数据成员有一点要注意只能通过构造函数的参数列表对常数据成员进行初始化,任何其他函数都不能对常数据成员赋值

class Date
{
public:
    Date(int year,int month,int date)   //通过构造函数参数初始化列表对常数据成员进行初始化
        :_year(year)
        , _month(month)
        , _date(date)
    {}
    void PrintDate()const   //声明PrintDate为常成员函数
    {
        cout << _year << "," << _month << "," << _date << endl;
    }
private:
    const int _year;
    int _month;
    int _date;
};

int main()
{
    Date const d(2018, 7, 26);   //声明d为常对象
    d.PrintDate();               //调用常成员函数PrintDate
    return 0;
}

这里写图片描述

3 常成员函数

void PrintDate()const ;  //定义一个常成员函数。

常成员函数可以引用const数据成员,也可以引用非const类型的数据成员。const数据成员可以被const成员函数引用,也可以被非const成员函数所引用。具体情况,看下表:
这里写图片描述

三 内联函数

1 概念: 其实内联函数很简单,就是以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数就是提升了程序运行效率
举一个求参数平方的例子:

inline double square(double x)   //声明square为内联函数
{
    return x*x;
}

int main()
{
    double a, b;
    double c = 12.0;
    a = square(5.0);
    b = square(1.5+2.5);
    cout << "a=" << a << ","<<"b=" << b << endl;
    cout << "c=" << c << endl;
    cout << "c square=" << square(c++) << endl;
    cout << "Now c=" << c << endl;
    return 0;
}

这里写图片描述

输出表明,内联函数和常规函数一样,都是按照值传递的方式传递参数。如果参数为表达式,则函数将传递表达式的值,这说明了C++的内联功能远远胜过C语言的宏定义(C语言用宏传递表达式必须加(),否则会出错)。
内联和宏:
inline工具是C++新增的特性。C语言使用预处理器语句#define来提取宏——–内联代码的原始实现。
例如:求一个数的平方的宏

#define SQUARE(x) x*x

这并不是通过传递参数实现的,而是通过文本替换来实现的 ——-x是“参数”的符号标记。

a=SQUARE(4);       //被替换为 a = 4 * 4
b=SQUARE(1+4);     //被替换为 a = 1 + 1 * 4 + 4    显然这个结果是错误的
c=SQUARE(c++);   //被替换为  a = c++ * c++

显然,上述的代码只有第一个是正确的,在C语言中我们会想到加括号来改进

#define SQUARE(x) ((x)*(x))

但是这样仍然存在问题,即宏不能按值传递。即使使用新的定义,SQUARE(c++),还是将c递增两次,但是在上面的程序当中,我们看到结果只是将c递增了一次。
这里说这么多,其实就是为了告诉大家在C++中,强制建议使用const代替宏常量,使用内联函数代替宏函数。const和内联函数在进行编译时不仅进行替换,还有严格的参数类型检测,提高了程序的安全性。
内联函数可以是普通函数,也可以是类的成员函数;函数式宏不能作为类的成员函数

内联函数的特性:
请容许我在BB这最后一点内容,讲一讲内联函数的特性。
⑴inline是一种以空间换时间的做法,省去了调用函数的额外开销。所以代码很长或者有循环/递归的函数不适宜使用内联
⑵inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为Inline的函数体含有循环和递归等,编译器会自动优化掉内联
⑶inline必须和函数定义放在一起,才能成为内联函数,仅将inline放在声明前是不起作用的
⑷定义在类内的成员函数默认为内联函数。

说到这里我就再说一道面试题:宏的优缺点?
答:
在《C陷阱与缺陷》中说道 宏并不是函数,宏并不是语句,宏并不是类型定义
优点:
1. 提高了程序的可读性,同时也方便进行修改;
2. 提高程序的运行效率:使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率;
3.宏是由预处理器处理的,通过字符串操作可以完成很多编译器无法实现的功能。比如##连接符。
缺点:
1 . 由于是直接嵌入的,所以代码可能相对多一点;
2. 嵌套定义过多可能会影响程序的可读性,而且很容易出错;
3. 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。

猜你喜欢

转载自blog.csdn.net/qq_39412582/article/details/81215272