C++类和对象补充

目录
  • 一. 再看构造函数
    • 1.函数体内赋初值
    • 2.初始化列表
      • 几点注意
    • 3.explicit关键字
  • 二.static成员
    • 1.概念
    • 2.特性
  • 三.友元
    • 1.友元函数
    • 2.友元类
  • 四.内部类
  • 总结

一. 再看构造函数

我们之前已经了解了构造函数的基本内容,那么这里我们将深入认识构造函数。

1.函数体内赋初值

class Date
{
public:
 Date(int year, int month, int day)
 {
 _year = year;
 _month = month;
 _day = day;
 //可以进行多次赋值,但一般不这么做
 _year = 1;
 }
private:
 int _year;
 int _month;
 int _day;
};

首先,对于构造函数体内的赋值我们不能称之为初始化。首先我们要理解:初始化只能初始化一次,而构造函数体内可以多次赋值。那么对象成员变量的初始化是在什么时候进行的呢?这就要接下来要介绍的初始化列表要做的事了。

2.初始化列表

初始化列表是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。其形式如下:

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
	{
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

几点注意

1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2.类中包含以下成员,必须放在初始化列表位置进行初始化:

(1)const成员变量:由于const变量初始化之后就不能更改,因此需在初始化列表进行初始化。

(2)引用成员变量:引用成员变量只能作为一个变量的引用,一旦初始化,就不能再作为其他变量的引用,因此引用变量也只能再初始化列表初始化。

(3)自定义类型成员变量(没有默认构造函数情况下):由于没有默认构造函数时,自定义类型变量是不能初始化的,此时程序也无法编译,因此没有默认构造函数的自定义类型成员变量必须在初始化列表进行初始化。

class B
{
public:
	B(int i)
		:_i(i)
	{
	}
private:
	int _i;
};
class A
{
public:
	A(int a, int& b, int bb)
		:_a(a)
		,_b(b)
		,_bb(bb)
	{
	}
private:
	const int _a;//const成员变量
	int& _b;//引用成员变量
	B _bb;//自定义成员变量
};

3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。比如下面代码的执行结果:

class B
{
public:
	B()
	{
		cout << "B()" << endl;
	}
private:
	int _i;
};
class A
{
public:
	A(int a, int& b)
		:_a(a)
		,_b(b)
	{
	}
private:
	const int _a;//const成员变量
	int& _b;//引用成员变量
	B _bb;//自定义成员变量
};
int main()
{
	int n = 0;
	A a1(0, n);
	return 0;
}

可以看到,初始化列表中并没有对自定义变量_bb初始化,但程序仍然调用了自定义类型的默认构造函数。

4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关,先想想下面的代码运行结果是什么:

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main()
{
	A aa(1);
	aa.Print();
	return 0;
}

可以看到的是,_a1为1,而_a2为随机值,这是因为在成员列表的声明中,_a2先被声明,_a1后被声明,因此初始化列表中的顺序是先_a2,后_a1。而一开始_a1为随机值,因此最终_a2为随机值。

3.explicit关键字

我们知道,对于构造函数,不仅可以构造和初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

比如Date类:

class Date
{
public:
	Date(int year)
		:_year(year)
	{}
	explicit Date(int year)
		:_year(year)
	{}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2020);
	// 用一个整形变量给日期类型对象赋值
	// 实际编译器背后会用2019构造一个无名对象,最后用无名对象给d1对象进行赋值
	Date d2 = 2021;//explict禁止隐式类型转换,因此该句代码运行错误
}

但是Date d2 = 2021;这样的代码可读性不是很好,因此可以使用explicit关键字将这种隐式类型转换禁止。

二.static成员

C语言中我们就接触了static关键字,那么这个关键字修饰成员会怎么样呢?

1.概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。像上面初始化列表中说的,静态的成员变量一定要在类外进行初始化。

2.特性

静态成员存储在静态区,为所有类对象所共享,不属于某个具体的实例

静态成员变量必须在类外定义,定义时不添加static关键字

类静态成员即可用类名::静态成员或者对象.静态成员来访问

静态成员函数没有隐藏的this指针,不能访问任何非静态成员;相对的,非静态成员函数可以通过this指针访问静态成员变量。

静态成员和类的普通成员一样,也有public、protected、private 3种访问级别,也可以具有返回值

接下来我们来看看一道题:

求1+2+3+…+n

题目描述:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
这道题我们可以利用构造函数,由于每次实例化对象,都会调用其构造函数,因此我们可以实例化n个对象,每次初始化时计算求和即可;

class Sum
{
public:
    //调用构造函数
    Sum()
    {
        _sum += _i;
        ++_i;
    }
    //static修饰的成员函数,没有隐含的this指针,只能访问静态成员变量
    static int GetSum()
    {
        return _sum;
    }
private:
	//static修饰的成员变量为所有定义出来的类对象共有
    static int _i;
    static int _sum;
};
//静态成员变量的定义
int Sum::_i = 1;
int Sum::_sum = 0;
class Solution {
public:
    int Sum_Solution(int n) {
        Sum* p = new Sum[n];
        return Sum::GetSum();
    }
};

【注意】sizeof(类名)不计算静态成员变量的大小。比如上述代码中的sizeof(Sum)为1,是一个空类。

三.友元

友元分为友元函数和友元类,其提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

1.友元函数

首先如果我们要重载<<(流插入)运算符,我们会发现将其定义成类成员函数将无法实现,这是因为类成员函数的第一个参数为this指针,那么我们只能将这个函数定义在类外,但是这样的话函数又不能访问类中的成员变量,那么这个时候要么在成员函数中实现访问的方法,要么就使用友元函数,使其可以访问类中成员。即:

class Date
{
	//用关键字friend在类中声明函数为Date的友元函数
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day;
	return out;
}
int main()
{
	Date d1(2021, 10, 20);
	cout << d1 << endl;
}

同理,cin也可以如此定义。

【说明】

1.友元函数可访问类的私有和保护成员,但不是类的成员函数

2.友元函数不能用const修饰

3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制

4.一个函数可以是多个类的友元函数

5.友元函数的调用与普通函数的调用和原理相同

2.友元类

和友元函数相似,友元类可以访问另一个类的私有成员。比如下面代码中,B作为A的友元类,可以访问A中的_a和_i。

class A
{
	//声明B为A的友元类,则在B中可以访问A中的成员
	friend class B;
public:
	A(int a)
		:_a(a)
	{
	}
private:
	int _a;
	static int _i;
};
class B
{
public:
	B(int b)
		:_b(b)
	{}
	static int Count()
	{
		A::_i++;
		return A::_i;
	}
private:
	int _b;
};
int A::_i = 0;
int main()
{
	A a1(1);
	B b1(1);
	cout << b1.Count() << endl;
	cout << b1.Count() << endl;
	return 0;
}

需要注意,友元关系是单向的,不具有交换性,比如上述代码中A不能访问B中的成员;友元关系不能传递,即B是A的友元,C是B的友元,但C不是A的友元,C就不能访问A中的私有成员。

四.内部类

顾名思义,定义在另一个类中的类就是内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。

内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

class A
{
public:
	class B//内部类,是A的友元类
	{
	public:
	//B可以直接访问A的成员
		void Print(const A& a)
		{
			cout << a._a << endl;
			cout << _i << endl;
		}
	};
	A(int a)
		:_a(a)
	{}
private:
	int _a;
	static int _i;
};
int main()
{
	A::B b1;//注意B的调用方式
	A a1(1);
	b1.Print(a1);
	//但A的对象不能去访问B中的成员
	a1.b1;//error
}

特性:

1.内部类可以定义在外部类的public、protected、private都是可以的。

2.注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。

3.sizeof(外部类)=外部类,和内部类没有任何关系。比如上面的sizeof(A)为4。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C++ 再识类和对象

    目录 类的6个默认成员函数 构造函数 1.概念 2.特性 隐式构造函数 无参和全缺省的函数均为默认构造函数 成员变量的命名风格 补充 析构函数 1.概念 2.特性 c++编译器在对象生命周期结束时自动调用析构函数 拷贝构造函数 1.概念 2.特性 若未显式定义,系统会生成默认的拷贝构造函数 浅拷贝的注意事项 总结 类的6个默认成员函数 一个类中如果什么成员都没有,那么这个类称为空类.空类中是什么都没有吗?其实不然,任何一个类,再我们不写的情况下,都会自动生成下面6个默认成员函数: 本篇文章将对这

  • C++入门浅谈之类和对象

    目录 一.面向过程vs面向对象 二.类的限定符及封装 三.类的实例化 四.this指针 五.默认成员函数 1. 构造函数 2. 析构函数 3. 拷贝函数 4. 赋值运算符重载 总结 一.面向过程vs面向对象 C语言面向过程,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题 C++是基于面向对象,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成,C++不是纯面向对象的语言,C++既有面向过程,也有面向对象可以混合编程.C语言面向过程,数据和方法是分离的.CPP面向对象,数

  • C++进一步认识类与对象

    目录 赋值操作符重载函数 1.运算符重载 2.赋值运算符重载 3.默认的赋值操作符重载函数 4.赋值重载函数与拷贝构造函数的对比 日期类的实现 const成员 1.const修饰类的成员函数 2.小结 取地址及const取地址操作符重载函数 总结 赋值操作符重载函数 1.运算符重载 C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似. 其函数名为: operator + 需要重载的运算符符

  • C++类与对象之运算符重载详解

    目录 运算符重载 加号运算符重载 左移运算符重载 递增运算符重载 递减运算符重载 赋值运算符重载 关系运算符重载 函数调用运算符重载 总结 运算符重载 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型 加号运算符重载 作用:实现两个自定义数据类型相加的运算 #include <iostream> using namespace std; class Person { public: // 构造函数 Person(int num1, int num2){ thi

  • C++初识类和对象

    目录 一.初步认识面向过程和面向对象 二.类的引入 三.类的定义 1.定义和声明全部放在类体中,需要注意的是: 2.声明与定义分离 四.类的访问限定符及封装 1.访问限定符 2.封装 五.类的作用域 六.类的实例化 七.类对象模型 1.计算类对象的大小 2.类对象的存储方式 八.this指针 1.this指针的引出 2.this指针的特性 总结 一.初步认识面向过程和面向对象 面向过程,关注的是怎么去做,比如在外卖系统中,强调点餐,做餐,送餐等一系列动作的方法,反映到语言中是函数方法的实现:而面

  • C++类和对象补充

    目录 一. 再看构造函数 1.函数体内赋初值 2.初始化列表 几点注意 3.explicit关键字 二.static成员 1.概念 2.特性 三.友元 1.友元函数 2.友元类 四.内部类 总结 一. 再看构造函数 我们之前已经了解了构造函数的基本内容,那么这里我们将深入认识构造函数. 1.函数体内赋初值 class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = d

  • java使用compareTo实现一个类的对象之间比较大小操作

    首先定义一个对象,注意这个对象必须实现Comparable接口,并重写这个接口的compareTo方法 package cn.demo; public class Student implements Comparable{ private int number=0; //学号 private String name=""; //学生姓名 private String gender=""; //性别 public int getNumber(){ return nu

  • 带你了解Java的类和对象

    目录 五丶封装 (1)包的概念与创建 1>概念 2>创建 (2)包的使用–导入包 (3)封装定义–权限控制访问 (4)补充些常用的包(小拓展) 六丶关于static成员 (1)修饰成员变量–区分成员丶静态成员变量 (2)修饰成员方法–调用私有变量 (3)访问私有属性 七丶代码块 (1)普通代码块 (2)构造代码块 (3)静态代码块 总结 五丶封装 (1)包的概念与创建 1>概念 在我们的电脑上有许多的文件,我们为了方便管理,大致给它们进行了不同的命名. 然后在不同的文件夹下面再给它们进行

  • Java 基础语法让你弄懂类和对象

    目录 Java 基础语法 一.类与对象的初步认知 二.类和类的实例化 三.类的成员 1. 字段/属性/成员变量 2. 方法 3. static 关键字 四.封装 1. private 实现封装 2. getter 和 setter 方法 五.构造方法 1. 基本语法 2. this 关键字 六.认识代码块 1. 什么是代码块 2. 本地代码块 3. 实例代码块 4. 静态代码块 七.补充说明 1. toString 方法 2. 匿名对象 八.总结 Java 基础语法 其实在学习 C 语言时就一直

  • C++类和对象到底是什么

    目录 1.C++ 中的类 2.面向对象编程(Object Oriented Programming,OOP) 1.C++ 中的类 C++ 中的类(Class)可以看做C语言中结构体(Struct)的升级版.结构体是一种构造类型,可以包含若干成员变量,每个成员变量的类型可以不同:可以通过结构体来定义结构体变量,每个变量拥有相同的性质. 例如: #include <stdio.h> //定义结构体 Student struct Student{ //结构体包含的成员变量 char *name; i

  • Java 类与对象详细

    目录 1.类 2.对象 3.练习 4.练习答案 前言: 早期的Java语言,是面对过程的语言(面向过程指把一个场景分割成一个个的步骤研究),如今的Java已经是面对对象的语言(面向对象指把一个场景分割成一个个的对象研究).面向对象是相比面向过程有很多便利的地方,以后读者会慢慢感受到~ 那么,何谓对象呢?小编正在使用的电脑是一个对象,读者手中的手机是一个对象--对象,指[一个][具体的]物品或者事物(注意对象可以是抽象的东西). 每个对象都有其特征和用途,不同类型的对象特征和用途有所不同.我们把具

  • JavaSE的类和对象你真的了解吗

    目录 1.基本概念 1.1面向对象 1.2类和对象 2.类的定义及使用 2.1定义 2.2类的实例化 3.this引用 3.1访问成员变量 3.2访问成员方法 3.3this引用的特性 4.构造方法 4.1构造方法的特点 4.2this在构造方法中使用 总结 1.基本概念 首先我们需要弄清楚几个概念:面向对象是什么.类是什么.对象又是什么?还是逐个来说 1.1面向对象 我们常说Java是面向对象的语言,C语言是面向过程的语言,那面向对象是什么,它和面向过程的区别在哪? 面向对象是解决问题的一种思

  • Java类和对象的设计原理

    目录 一.实验目的 二.实验代码 1.定义一个类MyProgram,包含两个属性: 2. 在Vehicle类的基础上创建一个Tractor(拖拉机)类 3. 组合实现汽车类 5. USB接口程序设计 6.this关键字主要有三个应用: 7.请简述static关键字的作用 8.请简述super关键字的作用 9.请简述final关键字的作用 一.实验目的 1. 掌握面向对象的编程思想.类与对象: 2. 掌握类的封装性.继承性和多态性的作用: 3. 掌握成员变量和成员方法的特性.构造方法.toStri

  • Python面向对象编程中的类和对象学习教程

    Python中一切都是对象.类提供了创建新类型对象的机制.这篇教程中,我们不谈类和面向对象的基本知识,而专注在更好地理解Python面向对象编程上.假设我们使用新风格的python类,它们继承自object父类. 定义类 class 语句可以定义一系列的属性.变量.方法,他们被该类的实例对象所共享.下面给出一个简单类定义: class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name =

随机推荐