类和对象(下)(二)

简介: 类和对象(下)(二)

友元

friend关键字修饰函数,使得这个函数是可以突破类的封装,但是友元会增加耦合度(关联程度),破环了封装,所以友元不能多用

分为:友元函数和友元类

友元函数就是在一个类中对于成员函数加上friend修饰,在类外进行定义

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

因为在类中成员函数会使得this指针占据第一个位置,对于某些功能如重载<<需要cin在第一个参数位置(与this就会争夺,但是this默认为第一个位置的形参),所以我们考虑到可以不使用成员函数,将重载<<放在全局域,但是这个时候因为private限制导致类外无法访问自定义类型,所以我们推出来友元这个定义,实际上就是一个普通函数,只是在类中声明的普通函数(没有this指针),但是因为在类中所以可以访问私有成员变量

注意:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

友元类

友元类实际上就是在另一个类中声明的一个类,使得这个友元类的所有(成员变量or成员函数)都可以访问该类的私有成员

//下面是对于友元类的例子
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class A {
public:
  int get() {
    return d._year;
  }
private:
  Date d;//创建Date类中的对象d ,然后就可以通过d来调用Date的私有成员变量了    
};
class Date
{
  friend class A;//声明A为Date的友元类
public:
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
  {}
  //重载>>运算符
   friend ostream& operator>>(ostream& out, Date& d);
  //{
  //  
  //}
private:
  int _year;
  int _month;
  int _day;
};
ostream& operator>>(ostream& out, Date& d)
{
  out << d._year << "-" << d._month << "-" << d._day << endl;
}
int main()
{
  return 0;
}

注意:

  • 友元关系的单向的,不具有交换性,如上述Date不能访问A的私有成员,但是A可以访问Date的私有成员
  • 友元关系是不能传递的
  • 友元关系不能继承,继承的话属于父子类,这个概念后序再讲

实际上友元类,在其中声明Date类成员,就是因为当我们需要使用这个Date类中的成员变量时候,通过friend访问访问私有or保护的成员变量,从而实现类外访问

内部类

内部类:如果一个类定义在另一个类的内部,就叫做内部类,内部类是独立的个体,不属于外部类,也并不能通过外部类的对象去访问内部类的成员,但是内部类是天生的友元类(外部类的友元)

特征:

内部类可以放在外部类的任何地方,不受访问限制符的限制

内部类可以直接访问外部类static成员,不需要外部类的类名or对象

sizeof(外部类)=外部类,内部类不影响外部类的大小

//例子
class A
{
  friend class C;
public:
  void get()
  {}
  class B//内部类  
  {
  public:
    void foo(const A& a)
    {
      cout << k << endl;//OK  //可以直接访问静态变量不需要类名or对象
      cout << a.h << endl;//OK
    }
  };
private:
  int h;
  static int k;
};
class C//友元类
{
public:
  void get(C& c)
  {
    a.h;
    a.k;
  }
  int date;
  A a;
};
int A::k = 0;
int main()
{
  return 0;
}

匿名对象

匿名对象的使用对于某些场景的情况下是比较好用的,匿名对象就是直接用类名加上(),如:A(),来使用的,创建这个对象之后,再下一行代码就会析构,所以匿名对象由此得名(不会留下痕迹)

//匿名对象的用法
class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }
    A& operator=(const A& aa)
    {
        cout << "A& operator=(const A& aa)" << endl;
        if (this != &aa)
        {
            _a = aa._a;
        }
        return *this;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};
int main()
{
  //正常实例化对象
    A a(1);//1调用构造函数,实例化对象a
    //当只有无参构造的时候(or默认构造函数) 实例化对象应该为 A a;//这样即可  不能加上()
    //A a();  这个代码是错误的,因为编译器无法判断你这个是函数的声明还是实例化对象
    //匿名对象的创建
    A(2);//这个就是匿名对象,我们可以发现,在编译器在运行到下一行的时候就会调用析构函数,销毁这个匿名对象
    //匿名对象的适合使用的场景,比如在,使用stl的时候,放进去vector容器,使用匿名对象,后序再说
    return 0;    
}

拷贝对象的编译器的优化

前文,我们讲到了,对于同一行的代码连续调用构造函数(or拷贝构造函数),按照我们的理论会正常进行调用这两个函数,但是编译器对于这个做法,进行了优化,使得无论理论上多少次调用,但是经过优化,只需要调用一次构造函数

class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    A(const A& aa)
        :_a(aa._a)
    {
        cout << "A(const A& aa)" << endl;
    }
    A& operator=(const A& aa)
    {
        cout << "A& operator=(const A& aa)" << endl;
        if (this != &aa)
        {
            _a = aa._a;
        }
        return *this;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};//我们继续使用这个类的模板
void f1(A aa)
{}
A f2()
{
  A aa;
  return aa;
}
int main()
{
    //非连续使用构造函数
    A a1(1);
    A a2=a1;//我们这样是分开的,不会进行优化
    //连续调用
    f1(A(2));//这样的话,理论上我们就会先进行匿名构造,然后传值拷贝构造创建临时对象赋值给aa,所以是连续两次构造,编译器进行优化为1次
    //当返回值为自定义类型A 的时候+赋值
    A a3=f2();//先f2的返回值需要调用拷贝构造,返回一个临时对象,然后需要赋值给a3(再次调用拷贝构造)   所以 (aa返回)拷贝构造 + (a3赋值)拷贝构造 = 优化为一次构造(一步到位,临时对象直接给a3)
    return 0;
}

首先我们要认识到赋值是什么,拷贝构造是什么,什么时候是赋值什么时候是拷贝构造

我们知道一点即可,是否已经创建了这个对象

如果早就创建了对象,那么就是赋值

如果没有,那么就是拷贝构造

new创建对象

对于c++、java这样的编程语言的话,那么我们一定知道new一个对象这样的说法,接下来先了解一下new

在C语言中我们去动态申请空间的时候,使用到malloc、calloc等函数,但是过程比较繁琐,需要强制类型转换等,对于C++来讲,我们也能动态申请空间,使用new关键字来实现

#include<malloc.h>
int main()
{
    //创建一个数组,动态申请空间
    int* a1=(int*)malloc(sizeof(int)*10);//这样是C语言的方式
    free(a1);//释放空间
    //c++
    int* a2=new int[10];//这样就可以啦
    delete[] a2;//delete关键字来释放空间,要根据类型,这个是new [] 所以释放空间的时候跟着[]
  //new也可以做到初始化
    int* a3=new int(10);//初始化数值为10,但是不能做到calloc那样,对于数组来进行全部的赋值(统一值)
    //int* a4=(int*)calloc(0,sizeof(int)*10)//a4[]数组初始化值都为0
    int* a5=new int[5]{1,2,3};//这样对于数组进行初始化是可以的(使用大括号)
    //前三位为1 2 3 后两位为0
    return 0;
}

实际上可以实现数组元素全部为0,如int* pa=new int[10]{};//这样就可以实现了类似于calloc(0,sizeof(int)*10);一样的效果

当然除了内置类型可以new,自定义类型也可以,但是这两个都需要用对应类型的指针的方式来接收,因为new是申请的空间(堆空间),返回的也是地址

//演示样例
class A
{
public:
    A(int a, int b)
        :_a(a)
        , _b(b)
    {}
    void Print()
    {
        cout << _a << " " << _b << endl;
    }
private:
    int _a, _b;
};
int main()
{
    A* a1 = new A(1, 2);//开空间+调用构造函数初始化  这个也进行了构造函数优化
    a1->Print();
    return 0;
}


相关文章
|
4月前
|
编译器 C++
【C++】类和对象(中)(2)
【C++】类和对象(中)(2)
|
4月前
|
存储 编译器 C++
【C++】类和对象(中)(1)
【C++】类和对象(中)(1)
|
存储 编译器 C语言
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)(二)
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)
57 0
|
存储 编译器 C++
【C++】类和对象(下)(二)
【C++】类和对象(下)
47 1
|
存储 算法 程序员
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)(一)
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)
57 0
|
编译器 C语言
类和对象(下)(一)
类和对象(下)(一)
|
C++
【C++】类和对象(下)(一)
【C++】类和对象(下)
50 0
|
编译器 C++
【C++】类和对象(下)(三)
【C++】类和对象(下)
66 0
|
存储 编译器 C语言
【C++】类与对象(上)(三)
【C++】类与对象(上)
60 0
|
存储 C语言 C++
【C++】类与对象(上)(二)
【C++】类与对象(上)
65 0