C++之——类和对象

一、类的定义:

如下代码:

class people
{
 char* name;
 char* sex;
 int age;
};

class就是类的关键字。people就是这个类的名字,{}为类的主体,里面有成员,变量叫做成员变量,函数叫做成员函数。 c语言中的struct也可以作为类的关键字。只不过在c中最好使用c的class写法。

struct people
{
 void print()
 {
  cout << "hello world\n";
 }
 int age;
 char name[20];
};

类的定义有两种形式: 一种是成员函数的声明和定义都在类的里面。 另一种是类的里面是成员函数的声明,在另一个文件中实现类的定义。

二、类的限定符
类的限定符有:public(公有) protected(保护) private(私有)
公有是别人是可以访问的, 私有为别人无法访问。 对于class的类默认访问权限是private;对于struct的类默认访问权限是public。

struct people
{
 int age;
 char name[20];
};
class p
{
 char* name;
 char* sex;
 int age;
};
int main()
{
 people a;
 a.age;//这个可以访问
 //p b;
 //b.age;//这个就不可以访问
 return 0;
}

我们在实现类的时候,想给别人使用的就用public限定成共有的。不想让别人使用的就限制成私有的。

class p
{
public:
 void print()
 {
  cout << "hello world\n";
 }
private:
 char* name;
 char* sex;
 int age;
};

三、类的作用域
类定义了新的定义域,里面的成员都属于该作用域。当在类的外面定义成员函数的时候,需要说明该成员的作用域。

class p
{
public:
 void print();
private:
 char* name;
 char* sex;
 int age;
};
void p::print()
{
 cout << "hello world\n";
}

四、类的实例化
实现一个类,当这个类没有创建对象的时候,它在内存中是没有开辟空间的。 类的实例化也就是用类创建对象。 类的大小只需要计算成员变量的大小(计算方法和c语言的一样)。 当类为空的时候,大小为1字节,只是为了占位。

class Test
{};
int main()
{
 std::cout << sizeof(Test) <<std::endl;
 return 0;
}

此时输出为:1;

而成员函数是放在公共代码区的,并不是在成员里面。

class P
{
public:
 void Print()
 {
  cout << "haha" << endl;
 }

};
int main()
{
 P* ptr = nullptr;
 ptr->Print();
 return 0;
}

在第13行的时候,编译的时候根本就没有解引用去找成员,而是直接去公共代码区去找的该函数的地址。

五、this指针

class Date
{
public:
 void Init(int year, int month, int day)
 {
  _year = year;
  _month = month;
  _day = day;
 }
 void Print()
 {
  cout << _year <<  ' '<<_month <<' '<< _day << endl;
 }
private:
 int _year;
 int _month;
 int _day;
};

int main()
{
 Date a, b;
 a.Init(1, 1, 1);
 b.Init(2, 2, 2);
 a.Print();
 b.Print();
 return 0;
}

输出:1 1 1 2 2 2
程序是怎么判断Init,Print是各自属于哪一个对象的? 其实有一个this指针,C++编译器在非静态成员函数增加了隐形的指针参数,调用函数的时候其实把对象的地址传递给了this指针。 this指针是编译的时候程序自己添加的,不需要我们自己手动添加。
this指针的特性:
1.this指针的类型是类的类型*const。
2.this不能在参数中显示,但是可以在函数体中的成员变量中可以使用。
3.this是成员函数的第一个隐形参数,存在栈区,因为是形参,具体存在哪里取决于编译器,不需要用户自己传递。
看下面这个代码:Print1就可以正常的运行,而Print2并不能,就是因为Print2需要对空指针解引用。编译的时候不会出错,因为不会去类中去找这个函数。

class Date
{
public:
 void Print1()
 {
  cout << "Print1()" << endl;
 }
 void Print2()
 {
  cout << _year << endl;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date* a = nullptr;
 a->Print1();
 a->Print2();
 return 0;
}

六、默认构造函数
6.1 构造函数
构造函数并不是创建一个函数,它是一种特殊的函数,给对象初始化用的。 构造函数的形式:

类名(参数)
{};

构造函数也可以重构,构造函数一般写在类里面实现形成内联函数。 当没有自己写构造函数的时候,会调用默认的构造函数,默认的构造函数只对自定义类型进行处理(调用它的构造函数),对内置类型不做处理。
内置类型:int double char 指针等等
自定义类型:类 等等
默认构造函数:全缺省,无参,自动生成的默认构造函数 因为不对内置类型处理,所以C++11又进行了弥补,在类的成员又加了一个默认值。
6.2 析构函数
析构函数的形式:

~类名()
{};

析构函数是释放类成员的资源。不是销毁对象。 析构函数可以自己写,当没有自己实现的时候会调用默认析构函数,默认析构函数对内置类型不做处理,对自定义类型会调用它的析构函数。 析构的顺序是先构造的后析构,后构造的先析构,局部先析构,全局/静态后析构。

6.3 拷贝构造函数
拷贝构造函数的形式:

类名(const类名&对象)
{};

如果你创建的对象想直接是另一个对象的值,就可以使用拷贝构造。

class Date
{
public:
 Date(int year = 1, int month = 1, int day = 1)
 {
  _year = year;
  _month = month;
  _day = day;
 }
 Date(const Date& d)
 {
  _year = d._year;
  _month = d._month;
  _day = d._day;

 }
private:
 int _year;
 int _month;
 int _day;
};

int main()
{
 Date a(2022, 8, 11);
 Date b(a);
 Date c = a;
 return 0;
}

第26,27行的是同一个意思,都是拷贝构造,不能把第27个看作是赋值。 关于为什么拷贝构造函数的参数是引用呢? 是为了放在死拷贝。如果是传值拷贝,在传参的时候就要拷贝,然后就业调用这个函数,调用这个函数又需要传值拷贝,最后还是要调用这个函数…无穷无尽。而传引用就避免了这个情况。 如果没有自己写拷贝构造函数,会调用默认构造函数,默认构造函数对内置类型进行值拷贝,对自定义类型会调用它的拷贝构造函数。

七、运算符重载
让对象也可以直接运用运算符, 运算符重载的形式:

返回类型operator运算符(参数)
{};

.* :: sizeof ?: .这5个运算符不能重载
7.1 赋值运算符重载
以日期类为例:

 Date& operator=(const Date& d)
 {
  _year = d._year;
  _month = d._month;
  _day = d._day;
        
        return *this;
 }

弄返回值是为了应对多次赋值的情况,引用左返回值和参数的原因是避免拷贝构造。 即使没有显式实现赋值运算符重载,也会生成默认的,其和拷贝构造一样,内置类型值拷贝,自定义类型调用它的赋值重载。
7.2 前置++/后置++重载
前置后置怎么实现呢? 前置,无参数 后置++,有一个int参数,只是为了去吧,没有什么实际意义。

    //前置
    Date& operator++()
 {
  _day++;
  return *this;
 }
    //后置
 Date operator++(int)
 {
  Date ret = *this;
  _day++;
  return ret;
 }

八、const成员
我们知道const修饰的不能改变,当const修饰的对象时,该对象的地址的类型是const 类*,而this指针的类型是类* const,会导致类型不匹配,所以此时要在类的成员函数后面加上一个const。 例如日期类:

class Date
{
public:
    bool operator==(const Date& d)
    {
        return _year == d._year && _month == d._month && _day == d._day;
    }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date a;
 const Date b;
 b == a;//这里就会报错
 return 0;
}

因为此时传过去的类型是const Date*,而this的类型是Date* const。 这样处理就可以解决这种问题了bool operator==(const Date& d)const。 只要是不改变this指针指向的内容都可以加上const。

九、&取地址操作符重载
这种操作符默认会生成,不需要自己写:

 Date* operator&()
 {
  return this;
 }
 const Date* operator&()const
 {
  return this;
 }

十、初始化列表
为什么要引用初始化列表?因为对于一些引用类型,修饰的类型const,自定义类型(特别是类中没有默认的构造函数)的时候。单靠在构造函数内部实现是不可能完成的。而初始化列表就可以解决这些问题。

它的形式是在函数后面加上:,有多个变量的时候以,分割。:类的成员(初始化), ,

初始化列表可以理解为成员变量定义的地方;成员变量声明的顺序是初始化的顺序。

class Time
{
public:
 Time(int hour)//此处非默认构造函数
 {
  _hour = hour;
 }
private:
 int _hour;
};
class Date
{
public:
 Date(int year)
  :_year(year)
  , _a(0)//该成员必须用初始化列表进行初始化
 {}
private:
 int _year;
 Time _a;
};
class f
{
public:
 f()
  :B(1)
  ,A(B)
 {
  cout << A << ' ' << B << endl;
 }
private:
 int A;
 int B;
};

十一、explicit关键字
取消对隐式类型的转换,对与单参数(下面的这种)或者第一个参数没有初始值(如:Date (int year, int month=0,int day=0)) 例:

class Date
{
public:
 explicit Date(int year=1)
  :_year(year)
 {}
private:
 int _year;

};
int main()
{
 Date a;
 a = 2022;//错误
 return 0;
}

这里的2022需要进行隐式转换,转换成Date类型,然后进行赋值操作。然而用explicit关键字可以阻止这中转换。此时那种转换就不正确了。

十二、static
12.1 static成员变量
static成员变量是静态变量,属于所有对象共享资源,当然它也受限定符的限定。静态的变量不能在构造函数(初始化列表)中进行定义。因为静态的木有this指针,它在全局进行定义。非静态的还是用构造函数进行初始。

class Date
{
public:
 Date()
  :_month(1)
 {}
private:
 static int _year;
 int _month;
};
int Date::_year = 1;

12.2 static成员函数
static成员函数也是所有对象共享的成员,在此函数里面只能访问静态成员,因为没有this指针。该成员函数的访问有两种方式:类名::函数,对象.函数

class Date
{
public:
 Date()
  :_month(1)
 {}
 static void Print()
 {
  cout << _year;
 }
private:
 static int _year;
 int _month;
};
int Date::_year = 1;
int main()
{
 Date a;
 a.Print();
 Date::Print();
 return 0;
}

十三、友元
关键字friend
13.1 友元函数
在<<运算符重载的时候,<<左边是流,右边是对象,在重载的时候因为第一个参数为this指针,会导致类型不匹配。除了写成全局的函数外,还可以用友元函数进行处理,设置成友元的时候,就没有了this指针了,就可以改变第一个参数了(即不然他为this指针)。 友元函数不属于任何类的成员,和普通的函数几乎没有区别。 类的友元函数可以访问私有成员。声明在类的类别,在类的外面进行定义。
下面这个是<<的运算符重载。

class Date
{
 friend ostream& operator<<(ostream& a, Date& b);
public:
 Date()
  :_year(1)
  ,_month(1)
 {}
private:
 int _year;
 int _month;
};
ostream& operator<<(ostream& a, Date& b)
{
 cout << b._year << ' ' << b._month;
 return a;
}

13.2 友元类
假如A是B的友元类,在B中可以访问A的私有成员。

class B
{
 friend class A;
private:
 int _b = 2;
};
class A
{ 
public:
 void Print(B b)
 {
  cout << _a<< ' ' <<b._b  << endl;
 }
private:
 int _a=1;
};
int main()
{
 A().Print(B());//匿名对象
 return 0;
}

十四、 内部类
内部类就是在一个类的里面再定义一个类,这就叫内部类。内部类是外部类的友元,即在内部类中可以访问外部类的私有成员。

class A
{
 class B
 {
 public:
  B()
   :_b(0){}
  void Print(A a)
  {
   cout << a._a;
  }
 private:
  int _b;
 };
public:
 A()
  :_a(0){}
private:
 int _a;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_41920323

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值