码迷,mamicode.com
首页 > 其他好文 > 详细

类与对象的认识

时间:2019-09-19 17:47:26      阅读:86      评论:0      收藏:0      [点我收藏+]

标签:私有   友元类   实例   细节   int   种类   操作   参数   ted   

类和对象

c是一门面向过程的语言关注的过程,c++作为一门面向对象的语言关注的时对象,把事情拆分为不同的对象,通过对象的交互来实现。

一.类和对象的初认识

1.类的引入
类用关键字class引出,与之前的结构体类似,不同的是结构体内不能定义函数而在类中可以定义函数啦(c++中结构体也可以定义函数哦)
2.类的定义
类中成员函数的两种定义方式:可以在类中定义定义,但编译器有可能将其当作内联函数来处理故推荐第二种——将函数的声明和定义分开。
3.类的访问限定符
public、protected、private--->protected和private修饰的对象在类外不能直接访问
class类默认是private,struct默认是public
面向对象的三大特性:封装、继承、多态,这里我们说一下封装的定义:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
4.类的作用域
类定义了一个新的作用域,若要访问类中的成员要用作用域限定符::进行访问。
在这里我们已经接触了四个作用域:全局作用域、函数内的局部作用域、类、命名空间。
5.类的实例化(对象)
类限定了有那些成员,类是没有空间开辟的,用类创建出的对象才有实际的内存空间。
到这里我们要思考一个问题,一个类的大小该如何计算呢??类中可以包含成员函数,此时又该怎么办呢??
通过测试我们可以知道类的大小其实就是类的成员变量的大小,类中的成员函数被放在公共代码区,要注意内存对齐的问题。如果是一个空类,或这个类中只有成员函数,呢么这个类的大小是1,因为创建对象时要有区分。
6.this指针
c++对每个成员函数增加了一个默认的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。呢么这个就相当于我们用户自己在传参时传入地址,正因为有this指针的存在所以函数才能准确的访问到调用它的主体。

  • this指针的特性
  • this指针的类型:类类型 * const,故this指针的指向是不能修改的。
  • this指针作为函数的形参,函数调用时将对象的地址传给this形参,故对象中不存储this指针

    二.类中的默认函数之构造函数

    一个类定义好的话里面并不是什么都没有,会自动生成六个默认函数
    1.构造函数
    构造函数是干什么的呢?
    构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。也就是说构造函数完成了对象的初始化,它的特点是:函数名与类名相同、无返回值、对象实例化时编译器自动调用对应的构造函数、构造函数可以重载。系统自动生成的是无参的构造函数,当然我们可以自己写一个带有参数的构造函数,那么我们在定义对象的时候就可以直接初始化拉,看下面的代码:
    class data<br/>{<br/>public:<br/>data(int year=1999,int month=1,int day=1)<br/>{<br/>_year = year;<br/>_month = month;<br/>_day = day;<br/>}<br/>private:<br/>int _year;<br/>int _month;<br/>int _day;<br/>};

那么在这个地方我们就完成了构造函数的创建,呢么在主函数内定义对象的时候就可以直接初始化对象:
data a (1999,10,13);

那么我们知道系统给的是无参的构造函数,所以我们在定义无参构造函数的时候就不需要带(),否则就变成了函数声明。
上述的代码就是我们显式创建的构造函数,当有它存在的时候编译器就不会再生成默认构造函数

还有一个注意的点:无参构造函数和全缺省构造函数都称作默认函数,是不允许重复定义的,只允许存在一个,故这两个是不能构成重载函数的。

呢么构造函数的作用是什么呢?-->因为变量类型不只是固定的int或char这种,也有可能是用户自定义类型,那么构造函数就会调用这种自定义类型的成员函数,这就是它的作用。

2。构造函数赋初值
上述过程中在构造函数体内的操作并不是初始化,那么我们习惯将用构造函数的初始化列表对其进行初始化。

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
public:
data(int year=1999,int month=1,int day=1)
:_year(year)
, _month(month)
, _day(day)
{

}

注意:1.就算没有初始化列表系统也会自动生成一个初始化列表,每个变量只能初始化一次,而且当成员变量是引用类型、const修饰、类类型成员变量(没有默认构造函数)这三种情况时必须使用初始化列表对其初始化,当一个没有默认构造函数的类类型成员变量不初始化系统也不会自动识别该如何初始化,故会出错。

2.成员变量的初始化顺序是按照成员变量的声明顺序决定的,与初始化列表的顺序无关,那么我们在初始化列表尽量于声明的顺序相同,更不要用变量进行赋值,极容易出错。

  1. explicit关键字
    在c++中发现了一个有趣的现象,data a = 100; 这条语句居然可以通过编译,等号两边的操作数类型不同却可以通过编译,于是我们可以研究一下这个复制重载在调用时的过程,进过测试我们发现在进行赋值运算符重载时首先调用了一次构造函数,为100构造出一个类!所以构造函数不光有构造的作用,对于单个参数(实参)的构造函数还有类型转化的作用。
    但有时我们不希望有这种情况的发生,用explicit修饰在构造函数前就会禁止这种类型转化。


3.static成员

在c++中static可以修饰成员变量和成员函数,由static修饰的变量或函数称作静态成员变量或静态成员函数
static修饰的为所有类对象共享的!

区别:
| 静态成员变量 | 普通成员变量 |
| 初始化在类外 | 类内初始化 |
| 所有对象共享的 | 每个对象都包含了一份 |
| 也可以通过类名::静态成员名访问 | 只能通过对象访问 |
| 所有对象共享的 | 每个对象都包含了一份 |

静态成员变量不会影响类的大小,也就是计算类的大小时不需要计算静态成员的大小,他们用的是同一块内存空间

| 静态成员函数 | 普通成员函数 |
| 没有隐藏的this指针 (不能访问非静态成员) | 有this指针 |
| 不能被const修饰 (因为const修饰成员函数实际上修饰的时this指针) | 可以 |
| 可以不通过对象调用 | 必须通过对象调用 |

用static修饰的静态成员函数只能访问静态成员变量,不能访问普通成员变量。
而普通成员函数都可以访问。

二.析构函数

析构函数与构造函数函数相对应,和构造函数构成函数重载,也就是说函数名也是类名,在函数名前加~代表析构函数,这个函数没有参数和返回值,析构函数并不是对类对象进行销毁,而是完成类的一些资源清理工作。对象在生命周期结束时会自动调用析构函数。

三.拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

注意:拷贝函数是对构造函数的一个重载,它的形参只有一个且必须船引用,如果传值调用会无穷递归。

那么有这个函数的存在我们在定义对象时就可以用已有的对象进行新对象的定义:
如:date d1; data d2(d1);

同样的,编译器也会为我们生成一个默认的拷贝构造函数,但它完成的时浅拷贝工作,比如下面的情况:

class String
{
public:
    String(const char* str = "jack")
    {
        _str = (char*)malloc(strlen(str) + 1);
        strcpy(_str, str);
    }

    ~String()
    {
        cout << "~String()" << endl;
        free(_str);
    }
private:
    char* _str;
};

int main()
{
    String s1("hello");
    String s2(s1);
}

在构造函数内我们完成了动态创建内存空间,这种情况默认拷贝构造函数就会原封不动的把这块地址拷贝到新对象d2中,这并不是我们想要的结果,d2和d1共用同一块内存空间对d2进行修改d1也会改变。这叫做浅拷贝,所以我们有必要自定义一种深拷贝的构造函数。这个后期在做介绍。

四.赋值运算符重载

在谈赋值运算符重载前我们有必要先谈一下什么是运算符重载,那么两个类是无法比较大小的,但是如果我们进行运算符重载,按照大小关系进行重载那么类也就可以使用比较运算符了,其它运算符也是一样的。

用关键字operator进行运算符重载
有一类特殊的运算符重载,就是前置++和后置++的重载,为了有所区别

赋值运算符主要有四点:

  1. 参数类型
  2. 返回值
  3. 检测是否自己给自己赋值
  4. 返回this//this作为返回值是为了解决连续赋值的问题
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

同样完成的也是浅拷贝,所以我们也有必要自己实现一个赋值运算符的重载。深拷贝的实现同样也放在后面说。

最后两种默认函数是普通变量和const变量的取地址,这两种很少自己实现。

四.友元

如果我们对输出运算符“<<”进行重载该怎么做呢
ostream & operator<<(ostream & _cout)------>这是对<<重载的函数函数,如果这样定义函数的话其中包含的this指针作为第一个形参,所以在调用函数想要输出一个类的时候就不能按照常规的cout<<d这种方式进行输出,函数的第一个参数是this指针,所以调用时要d<<cout,这样才能正常的输出。
那么我们怎么能避免这种方式而是按照常规的方式对<<进行重载呢

这里就要我们就要在全局作用域定义一个重载函数把两个参数按照正常的顺序传入,但是在类中就不能不能引用这个函数的私有成员,我们只要在类中声明这个函数用friend关键字修饰那么这个函数就是这个类的友元函数也就可以在类内访问了

注意:
友元函数是普通函数并不是类中的成员函数
友元函数可访问类的私有成员,但不是类的成员函数
友元函数不能用const修饰(因为const修饰的是this指针,而友元函数并没有this指针)
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用和原理相同

同样一个类也可以是另一个类的友元类,这个类就可以访问其内部的私有成员了,但注意此过程是单向的且不能传递。

类与对象的认识

标签:私有   友元类   实例   细节   int   种类   操作   参数   ted   

原文地址:https://blog.51cto.com/14239789/2439354

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!