您现在的位置是:首页 > 正文

C++学习15_类和对象(特殊数据成员初始化 特殊成员函数)

2024-01-30 22:50:31阅读 0

特殊的数据成员——初始化

4类比较特殊的数据成员 常量成员、引用成员、类对象成员和静态成员,其初始化与普通数据成员有所不同。

常量数据成员 ( const ) 和引用数据成员( & )

const数据成员具有“只读属性”,经初始化后,在程序中无法修改其值。

因此必须在初始化列表中初始化常量数据成员,不能使用赋值表达式 (=)。

和常量成员相同,引用成员也必须在初始化列表中进行初始化。

class Point{ 
public:
	//错误写法
    Point(int ix = 0, int iy = 0)
    {
        _ix = ix;//error, const成员不能使用赋值语句修改/初始化
        _iy = iy;//error
        _iz = _ix;
    } 
    //正确写法
    Point(int ix = 0, int iy = 0){ 
        : _ix(ix), _iy(iy), _iz(_ix) //初始化列表中initialize
    }
private:
    const int _ix;
    const int _iy;
    int & _iz;	//
};

可以用 sizeof(Point) 测出类又多长,

可以看出占据类空间的只有数据成员,没有成员函数。

一个空类占1个字节(1 bit)sizeof(Empty) = 1;

类对象数据成员

当类中的数据成员本身也是类对象时,对象数据成员的创建及初始化必须放在类中构造函数的初始化列表中。

默认情况下,类对象数据成员会在初始化列表中进行初始化,此时只会调用子对象的默认构造函数,如果默认构造函数没有,会报错,所以为了符合要求,最好在初始化列表中进行显示初始化。

如一个直线类Line对象中包含两个Point类对象,对Point对象的创建就必须要放在Line的构造函数的初始化列表中进行。

//Line线类
class Line{
public:
    //构造函数
    Line(int x1, int y1, int x2, int y2)
    : _pt1(x1, y1), _pt2(x2, y2) //类对象成员要在初始化列表中initialize
    {
    	cout << "Line(int,int,int,int)" << endl;
    }
    //打印函数
    void printLine(){
        _pt1.print();
        cout << "---->";
        _pt2.print();
        cout << endl;
    }
private:
    Point _pt1;
    Point _pt2;
};
//当Line的构造函数没有在其初始化列表中初始化对象_pt1和_pt2时,
//系统也会自动调用Point类的默认构造函数,此时就会与预期的构造不一致。
//因此需要显式在Line的构造函数初始化列表中初始化_pt1和_pt2对象。

//Point点类
class Point{
public:
    //构造函数
    Point(int x = 0, int y = 0):_ix(x),_iy(y){
        cout << "Point(int = 0, int = 0)" << endl;
    }
    //打印函数
    void print(){
        cout << "(" << this -> _ix 
             << "," << this -> _iy
             << ")" << endl;
    }
//数据成员
private:
    int _ix;
    int _iy;
}
//主函数
int main{
    Line line(1, 2, 3, 4);
    cout << "line = " ;
    line.printLine();
    return 0;
}

静态数据成员 ( static )

静态数据成员不能在初始化列表中进行初始化,要放在全局静态的位置进行初始化。

  1. 静态数据成员和静态变量一样,是在编译时就被创建并初始化的(,当程序执行时,该成员已经存在,一直到程序结束。
  2. 静态数据成员存储在全局/静态区,任何该类对象都可对其进行访问,不占据对象的存储空间。
  3. 静态数据成员的实例只有一个,被所有该类的对象共享。因此静态数据成员不占用类的大小(即类对象的空间,因为静态数据成员数被类创建的所有对象共享的)。
  4. 对于头文件与实现文件分开书写的形式,静态数据成员的初始化要放在实现文件中进行,否则会出现重定义问题。
//举例代码:
class Computer
{ 
public:
    //构造函数
    Computer(const char *brand, double price)
    : _brand(new char[strlen(brand) + 1]()), _price(price)
    {
    	_totalPrice += _price;
    } 
    //打印函数
    void print()
    {
        cout << "品牌:" << _brand << endl
        	 << "价格:" << _price << endl
        	 << "总价:" << _totalPrice << endl;
    } 
    //...
private:
    char * _brand;
    double _price;
    //定义静态变量_totalPrice获取总价
    static double _totalPrice;
};

因为static数据成员不属于类的任何一个对象,所以它们并不是在创建类对象时被定义的。

即它们不是由类的的构造函数初始化的。

一般来说,不能在类内部初始化静态数据成员,必须在类的外部定义和初始化静态数据成员,且不再包含static关键字,格式如下:

数据类型 类名::变量名 = 初始化表达式; 	//普通变量
数据类型 类名::对象名(构造参数);		 //对象变量

//举例代码:
//Computer中的静态变量_totalPrice的初始化如下:
double Computer::_totalPrice = 0;

特殊成员函数

C++类中还有两种特殊的成员函数:静态成员函数和 const 成员函数。

静态成员函数

成员函数也可以定义成静态的,静态成员函数的特点:

  1. 静态的成员函数的第一个参数位置没有this指针,因此有以下情况。
  2. 静态成员函数只能访问静态数据成员和静态成员函数
  3. 静态成员函数不能访问非静态数据成员和非静态成员函数
  4. 非静态的成员函数能访问静态的数据成员和静态的成员函数
  5. 由于静态成员函数由所有对象共享,所以可以使用 类名::函数名 的形式调用静态成员函数(特殊用法)。
//举例代码:
class Computer
{
public:
    //构造函数
    Computer(const char * brand, double price)
    : _brand(new char[strlen(brand) + 1]()), _price(price)
	{
        _totalPrice += _price;
	} 
    //...
	static void printTotalPrice()
    {
    	cout << "总价:" << _totalPrice << endl;
        /* this -> _price = 10; //error,静态成员函数无this指针。 */
        /* printf( "this : %p\n", this); //error,静态成员函数无this指针 */
        /* print(); //error,不能调用非静态成员函数。 */
    } 
    ~Computer(){
        cout << "~Computer()" << endl;
        _totalPrice -= _price;
    }
    //...
private:
    char * _brand;
    double _price;
    static double _totalPrice;
};

double Computer::_totalPrice = 0.0;		//全局位置初始化静态数据成员

int main(void)
{
    cout << "初始情况下,总价为:" << endl;
    Computer::printTotalPrice();//不用对象,直接通过类名调用静态成员函数
    
    //创建pc1对象
    Computer pc1("Lenovo YOGA 14s", 6500);
    pc1.print();
    pc1.printTotalPrice();
    
    //创建pc2对象
    Computer pc2("Huawei MateBook14", 7000);
    pc2.print();
    
    //以下三种效果相同,输出结果相同
    pc2.printTotalPrice();
    pc1.printTotalPrice();//pc1 和 pc2 共享 printTotalPrice()
    Computer::printTotalPrice();//通过类名直接调用
    /* Computer::print() //error,不能通过类名直接调用非静态成员函数 */
    return 0;
}

const成员函数

const放在函数的参数表和函数体之间(与之前介绍的const放在函数前修饰返回值不同),

特点:

  1. 只能读取类数据成员,而不能修改数据成员。
  2. onst版本的成员函数具有只读特性,不能进行写操作,所以若想实现不修改数据成员,可以将成员函数用const修饰。
  3. const版本的成员函数与非const版本的成员函数可以重载( 参数列表中this指针const属性不同 ),一般先写const版本的。
  4. const对象只能调用const版本额成员函数,不能调用非const版本的成员函数;
  5. 非const对象,既可以调用const版本的成员函数,可以调用非const版本的成员函数。
  6. 对于非const对象,若屏蔽掉非const版本的成员函数,则调用const版本的成员函数,但若屏蔽掉const版本的成员函数,则const对象调用时会报错。
  7. 默认情况下,非const对象调用非const版本的成员函数。

格式:

函数返回类型 函数名(参数列表) const {
	//函数体
}  

举例代码:

class Computer
{ 
public:
	//...
    //原本this指针的形式:单const,*const,可以修改所指内容,不能修改指针指向
	void print(/* Computer *const this */) {
        /* this -> price = 100; //OK,可以修改所指内容 */
        cout << "void print()" << endl;
        cout << "品牌:" << _brand << endl
        	 << "价格:" << _price << endl;
    }
    //加const后的this指针的形式:双const,const * const,不能修改所指内容和指针指向
    //故参数列表不同,实现了函数重载
    void print(/* const Computer *const this */) const {
        /* this -> price = 100; //error,不可以修改所指内容 */
        cout << "void print()" << endl;
        cout << "品牌:" << _brand << endl
        	 << "价格:" << _price << endl;
    }
    //...
};

void test(){
    Computer pc1("Lenovo YOGA 14s", 6500);			//非const对象
    cout << "pc1 = " << endl;
    //调用非const版本(默认)或const版本的print成员函数均可
    pc1.print();
    
    const Computer pc2("Lenovo YOGA 14s", 6500);	 //const对象
    cout << "pc2 = " << endl;
    //只能调用const版本的print成员函数
    pc2.print();
}

r pc1(“Lenovo YOGA 14s”, 6500); //非const对象
cout << "pc1 = " << endl;
//调用非const版本(默认)或const版本的print成员函数均可
pc1.print();

const Computer pc2("Lenovo YOGA 14s", 6500);	 //const对象
cout << "pc2 = " << endl;
//只能调用const版本的print成员函数
pc2.print();

}


网站文章

  • SSL协议详解

    SSL协议详解

    SSL协议分为两层,下层为==SSL记录协议==,上层为==SSL握手协议、SSL密码变化协议和SSL警告协议。== 1.下层为SSL记录协议,主要作用是为高层协议提供基本的安全服务 &gt; 建立在...

    2024-01-30 22:50:24
  • Cannot invoke “org.apache.ibatis.session.SqlSession.close()“ because “sqlSession“ is null

    Cannot invoke “org.apache.ibatis.session.SqlSession.close()“ because “sqlSession“ is null

    MyBatis遇到的神坑 刚才去优化MyBatis的属性配置的时候,使用resultMap,执行测试方法,提示我错误信息 可以看到错误信息 Cannot invoke &quot;org.apache...

    2024-01-30 22:50:17
  • 计算机组成原理名词解释 地址,计算机组成原理名词解释

    匿名用户1级2009-12-27 回答计算机是一种用于高速计算的计算机器,可以进行数值,逻辑计算,并具有存储记忆功能。如果没有没有特别指定,现在的计算机都是指电子计算机。电子计算机由软件和硬件两大系统...

    2024-01-30 22:50:09
  • Android RTL布局适配

    Android RTL布局适配

    丈夫志四海,万里犹比邻。 —曹植《赠白马王彪》 RTL布局是什么 如上图所示,RTL(Right To Left)即视图的表现形式是从右开始向左结束。我们日常更习惯于LTR(Left To Right)视图,但在中东的阿拉伯语系里他们视觉习惯跟我们正好相反,他们更习惯从右向左的视觉形式。 最近公司开发的产品正好需要做RTL适配,本文总结分享在适配RTL过程中的技术要点。 RT...

    2024-01-30 22:49:41
  • Java集合之ArrayList和LinkedList

    Java集合之ArrayList和LinkedList

    Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set ...

    2024-01-30 22:49:33
  • 08 JavaScript

    08 JavaScript

    1. 基本介绍1.JavaScript是一种专门在浏览器编译并执行的编程语言2.JavaScript处理用户与浏览器之间请求问题3.JavaScript采用【弱类型编程语言风格】对【面向对象思想】来进...

    2024-01-30 22:49:26
  • Ant Counting(多重背包计数 + 优化)

    Ant Counting(多重背包计数 + 优化)

    Ant Counting 题目 t种蚂蚁,总共n只,从中挑出i只,求从中挑出l只到r只的所有挑法。 思路 显然是dp , 定义dp为从前i只挑出j只的方案集合,记录它的数量count。 状态转移是从 ...

    2024-01-30 22:48:59
  • logStash导出elasticsearch数据

    需求:要分析es库中存储的数据。 注:es存储数据是以文件形式存储的没有办法直接分析,需要把数据以.json文件形式的数据来进行分析。 环境:linux,logstash7.1.1,elasticse...

    2024-01-30 22:48:53
  • 设计模式之适配器模式(C++实现)

    设计模式之适配器模式(C++实现)

    适配器模式介绍,包括应用场景,优缺点,模式结构,类图 以及 C++代码示例

    2024-01-30 22:48:47
  • 安装kafka视图工具

    执行命令java -cp KafkaOffsetMonitor-assembly-0.2.0.jar com.quantifind.kafka.offsetapp.OffsetGetterWeb --zk 192.168.49.204:2181 --port 8888 --refresh 10.seconds --retain 2.days即可...

    2024-01-30 22:48:19