C++_类和对象

一.封装性(访问限定符实现)

  • 数据和方法封装到一起

  • 访问限定符

    三种访问限定符:public,protected,private

    1.public成员可从类外直接访问,private/protected成员不能从类外直接访问

    2.访问限定符的作用域是从出现到下一个限定符出现之前,或者到类体的结束前

    3.类体中如果没有访问限定符,默认private

    4.类的访问限定符体现了面向对象的封装性

    5.class的默认权限是private,struct的默认权限是public

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

与结构体的不同:有访问限定符,结构体默认是共有  如果不想用访问限定符限制,C++就用struct

耦合度:独立性

作用域:局部域,全局域,类域,名字空间域(花括号括起来的都可以看做一个域)

类的作用域:

1.每个类都有自己的作用域,类的成员都在这个类的作用域中,成员函数内可任意访问成员变量和其他的成员函数

2.对象可以通过.访问公有成员,对象的指针通过->来访问公有成员

3.头文件放声明,不用定义类的方法,声明不开辟空间,定义分配空间

class Person                     在类外定义

{                                      void Person::fun()

    void fun();                    {;}

};      .h中

 

在使用一个变量前,必须先进行声明,在类中,成员变量在函数之后,为什么编译器不报错?

在类中定义的名称(类的成员变量和成员函数)的作用域为整个类,作用域为整个类的名称只在该类中是已知的

类实例化对象  Person p;

 

1.一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间存储类成员变量  (类中的函数放在代码段)

2.类只是模型一样的东西,限定有哪些成员,定义一个类并没有分配实际的内存空间

3.类和对象就像图纸和房子

4.对象的大小为类中的所有成员变量的大小之和,同时也要遵循内存对齐

内存对齐

1.第一个成员从结构体偏移量为0的地址处开始存放

2.其他成员变量要对齐到某个数字(1对齐数)的整数倍的地址

   //对齐数=编译器默认对齐数 与该成员大小的较小值

  VS中默认为8  gcc中的默认值为4

3.结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍

4.如果嵌套结构体,嵌套结构体对齐到自己最大对齐数的整数倍处,整体大小仍要满足最大对齐数(包含嵌套结构体的对齐数)的整数倍

为什么要内存对齐?(面试题)

内存的访问不是任意位置的访问  CPU默认一次访问4个字节或8个,按内存对齐可以更快访问到需要的内容

是一种以空间换时间的机制

空类的大小是什么?为什么?空类就什么都没有吗?(面试题)

 一个字节,是为了占个位置,表示这个类型的对象存在过,有六个默认成员函数

封装性体现在两个方面:类封装了成员和方法,访问限定符

 

面试题:如何在类外访问一个类中的成员变量

1.设置set/get接口

2.友元函数//友元类

this指针

       1. this指针的类型:类类型 * const

  1. this指针并不是对象本身的一部分,不影响sizeof的结果

  2. this指针的作用域在类的非静态成员函数内部

  3. this指针是类成员函数第一个默认的隐含参数,编译器自动维护,不用显式传递

  4. 只能在类的非静态成员函数中使用,其他函数不可以

this指针遵从_thiscall调用约定:

    1._thiscall只能用在成员函数上

    2.参数从右向左压栈

    3.参数个数确定,this指针通过ecx传递给被调用者;如果参数不确定(_cdecl),this指针在所有参数被压栈之后再压入堆栈

    4.对参数个数不确定的,调用者自行清理堆栈,否则函数自己清理

this指针可以为空,但不要调用有this指针的地方,会访问非法内存  Date * d=NULL; 此时this的值为NULL

为什么使用的是指针,而不是引用。

this指针的类型和类引用的类型都是Test* const但是,由于历史原因,C++最初是没有引用,而有指针的,在设计类时,就采用了指针的方式,就导致了现在的局面。虽然引用理论上也可以实现this的功能。

(5)this指针不能在构造函数的初始化列表中给对象的成员变量赋值,

初始化列表本义是在创建类的实例对象时,给其中成员变量赋初值。即此时对象还未创建完毕。而this指针是类在创建之后,由编译器自动生成的指针,指向对象的首地址。简单来说,先有对象,后有this指针。所以this指针不能在初始化列表中给成员指针赋初值。

this指针的调用原理就是指向类的起始位置(取类的地址),通过加上偏移量找到对应的成员变量,再对相应的成员变量进行操作

 

日期类-->时间网站

成员变量加_和参数区分

this(就是一个形参)-->存在栈上  VS中通过ecx传递

定义和初始化分开就不是原子操作

 

类的指针可以为空,且能正确调用没有this指针的成员函数

 

类的6个默认成员函数:构造函数,拷贝构造函数,析构函数,赋值运算符重载,取地址操作符重载,const修饰的取地址操作符重载

 

构造函数的作用:

1.构造&初始化对对象(原子操作)

2.类型转换,对于单参的构造函数,可以将其接收参数转化成类类型对象

用explicit修饰构造函数,抑制由构造函数定义的隐式转换,explicit关键字类内部的构建声明上,在类外的定义上不用再写

class Date

{

public:

             Date(int  n)

            {

                         _year = n;

                        cout << _year << "  " << _month << " " << endl;

            }

            Date()

            {

                        cout << _year << endl;

            }

private:

            int _year;

            int _month;

            int _day;

};

int main()

{

            Date d;

            d = 19.2;   此时d先调用 Date(),然后再调用Date(int n),发生了强制类型转换,若前面加了explicit关键字,编译器会报错,找不到可用函数,d的_year也变为19

过程:使用构造函数Date(int n)来创建一个临时的Date对象,并将19.2作为初值。随后,执行函数中的以下语句,将该临时对象中的内容复制到d中

隐式类型转换只发生在只接收一个参数的构造函数,这个过程叫隐式转换,自动进行

构造函数为什么不能为虚函数?

构造函数为什么不能用const修饰?构造函数的作用是构造和初始化对象,加了const无法对成员变量值进行修改,也就无法初始化了

构造函数的特性:函数和类名相同,没有返回值,编译器自动调用,可以重载(拷贝构造函数),缺省的构造函数(无参和带缺省值)只能有一个,有初始化列表,没有显示定义,编译器自动合成

 

在拷贝构造函数中,为什么对象可以直接访问私有的成员变量,也可以用初始化列表

1.在类的成员函数中可以直接访问同类的私有/保护成员

2.C++的访问限定符是以类为单位的,也就是说这个单位内的成员可以互相访问

拷贝构造函数必须使用类类型的引用传递参数(传值会引发无穷递归,没有出口,编译报错)

如果没有显示定义,编译器自动默认合成,会对每个成员变量依次拷贝(若拷贝malloc等申请空间的函数,会释放两次,因为直接拷贝了地址)

            Date d(2018,4);

            Date d1= d;  也会自动调用拷贝构造函数

不能重载的运算符(面试题

5个不能重载的运算符:•(成员选择符)/.*(成员对象选择符)/::(域解析操作符)/?:(三目运算符)

除了赋值号(=)外,基类中被重载的操作符都将被派生类继承

前置++  ++d-->d.operator++(&d)       Date operator++()

后置++    d++-->d.operator++(&d,0)  Date operator++(int)

在重载运算符=时,要加判断if(this != &d)避免多余的赋值 

 

//两个日期比较大小

bool operator < (const Date& x1,const Date& x2)

{

            if (x1._year<x2._year)

                        return true;

            else if (x1._year == x2._year)

            {

                        if (x1._month < x2._month)

                                    return true;

                        else if (x1._month == x2._month)

                        {

                                    if (x1._day < x2._day)

                                                return true;

                                    else

                                                return false;

                        }

            }

}

cout << operator<(d1, d2) << endl;  调用

 

d1<d2->d1.operator<(&d1,d2)

深度探索构造函数

  

 

初始化列表不写,编译器也会自动初始化---自定义类型的成员变量

初始化列表:尽量使用初始化列表初始化,因为更高效(两个类嵌套,一个类在另一个类的构造函数中初始化)

初始化列表可以认为成员变量定义的地方

仅用于初始化类中的成员变量

一定要在初始化列表初始化的数据

尽量避免使用成员初始化成员(会调用其他函数),初始化顺序和定义顺序一致

const类型数据,引用,没有缺省构造函数的自定义类型的成员变量

哪些类必须自己写构造函数:有const类型的成员变量,引用

成员变量按声明顺序依次初始化,而非初始化列表出现的顺序

            Date(int year, int month, int day,int time ,int minute,int second)

                        :_year(year)

                        , _month(month)

                        , _day(day)

            {

                        _year = year;

                        _month = month;

                        _day = day;

                        _t = Time (time, minute, second);  //此处使用匿名对象,因为用一次就不再用了

            }

 

const修饰成员函数,实际修饰的是隐含的this指针,表示函数中不能对类的成员进行修改

const修饰成员变量,一定要在初始化列表初始化

在成员函数

const Date*  修饰指向的对象

Date const*   修饰指向的对象  const在*之前修饰的是指向的内容

Date * const  *在const之后修饰的是指针变量本身

const Date* const         两者都指向

const不能传给非const

在const修饰的成员函数中要对类的某个数据成员进行修改,该数据成员定义或声明时必须加mutable关键字

class Date

{

public:

            void Set() const

            {

                        this->_month = 10;

            }

private:

            int _year=2018;

            mutable int _month;  //可以在const的情况下对变量进行修改

};

const Date d;可以调用以下函数

void Display()const

 

取地址运算符(这两个默认的成员函数一般不用重新定义)(可以让别人取不到你的地址)

            Date* operator& ()

            {

                        return this;

            }

            const Date* operator&() const

            {

                        return this;

            }

 

析构函数

特性:一个类中有且只有一个,若未显式定义,编译器自动生成  无参数无返回值,~类名,对象生命周期结束时,编译器自动调用,析构函数的作用不是删除类的实例化对象,而是做一些清理工作

 

内联(inline)

减少函数压栈的开销,提高程序的运行效率 

在realease调试下,是一种以空间换时间的做法,代码很长或者有递归的函数不适合使用内联

内联就是建议性函数(代码很长或者递归循环不适合使用内联函数 ,内联对于编译器只是个建议,编译器会自动优化,如果定义 )编译器会自动优化,代码很长或者递归编译器可能就不会展开,忽略掉内联

定义在类内的成员函数默认定义为内联函数 

 

为什么声明和定义要在两个文件中?

1,方便约定

2.更深层次的封装,闭源

3.提高编译速度、

 

比较小的函数直接在类中写出来

 

宏的优缺点

宏:增强程序的可维护性,在编译时展开

    缺点:没有类型安全的检测,不方便调试,可读性差

尽量以const,enum,inline替换#define

注意:在C++中,强制建议使用const代替宏常量,使用内联函数代替宏函数。const和内联在进行编译时不仅进行替换,而且还会进行参数类型检查,提高了程序的安全性。内联函数可以是普通函数,也可以是类的成员函数;函数式宏不可以作为类的成员函数

 

友元(friend)        优点:提高了程序的运行效率

友元函数在类中声明

    友元函数可以直接访问类的私有成员变量,它是定义在类外的普通函数,不属于任何类,但需要在类的内部进行声明

    说明:

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

        2.友元函数不能在函数名后用const修饰,函数名前加const可以,表示返回值是常量

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

private:

            int _year;

            int _month;

            int _day;

            friend ostream& operator<<(ostream& out, const Date& d);

};  声明在私有成员下面也可以正常调用

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

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

友元类:???

    友元类的所有成员函数都可以是另一个类的友元函数,都可以访问一个类中的非公有成员

cout ostream类中的对象

//d.operator<<(cout)  out也是一个类

void operator<<(ostream& out)

{

            out << _year << "-" << _month << endl;

}

            d1.operator<<(cout);

            d1 << cout;  这样调用很奇怪

 

void operator<<(ostream& out, const Date&d)

{

            out << d._year << "-" << d._month << endl;

}

friend void operator<<(ostream& out, const Date&d);

operator<<(cout,d1);

            cout<<d1 ;   这样不能连续调用也不对

friend ostream& operator<<(ostream& out, const Date&d);

ostream& operator<<(ostream& out, const Date&d)

{

            out << d._year << "-" << d._month << endl;

            return out;

}

cout << d1 << endl;

            cout << d1; 终极版本

istream& operator >>(istream& in, Date& d)

{

            in>> d._year;

            in>> d._month;

            return in;

}              cin >> d1;  

输入输出必须用友元,友元破坏了封装性,不宜多用

注意:

    友元关系不能继承。友元关系是单向的,不具有交换性。友元关系不能传递

 

只有类的成员函数才能在函数名后面加上const,这时成员函数叫做常量成员函数

用意是不能通过this指针修改类对象成员变量的值、

含义:const修饰的是类函数隐藏的第一个参数 this指针,这表明this指针只读,也即类成员不可修改

注意该用法只能是成员函数,要是类的静态函数或者是非成员函数就不可以在函数名后面加上const

1) 函数名前加const

const int Func() {}

含义:返回值不可修改

 

静态成员(static)

静态成员变量在类外初始化,在类中声明加static,定义时不用加关键字

静态成员的调用  类名::静态成员  对象.静态成员

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

在main函数之前已经初始化

通过函数可以get值,但不属于某个对象的东西

生命周期长

 

static修饰成员函数没有this指针,只能调用静态成员变量,可以用const修饰

静态成员变量不占类的大小,放在全局变量区

非静态的成员函数可以调用静态成员

静态成员变量可以通过静态成员函数来调用

class Date

{

public:

                        Date()

                        {

                                    count++;

                        }

                        Date(int year,int month, int day)

                                    :_year(year)

                                    , _month(month)

                                    , _day(day)

                        {

                                    count++;

                        }

                        Date(const Date& d)

                                    :_year(d._year)

                                    , _month(d._month)

                                    , _day(d._day)

                        {

                                    count++;

                        }

                        static void print()

                        {

                                    cout << Date::count << endl;

                        }

private:

            int _year;

            int _month;

            int _day;

            static int count;

};

int Date::count = 0;

int main()

{

            Date d;

            Date d1(2018, 8, 2);

            Date d2 = d1;

            Date::print();

}

编译器的优化(调用了几次构造,拷贝构造,赋值,析构)3种情况

1.传参

    传值void Fun(Date d){} 1次构造-->1次拷贝构造-->2次析构

    传引用void Fun(Date& d){}  1次构造,1次析构

2.传返回值

Date fun2()

{

            Date d;

            return d;         return Date();-->调用了一次构造,一次析构

}          1次构造-->1次拷贝构造-->2次析构

 

匿名对象在构造的下一步就销毁

Date();执行完这条语句就销毁

 

void Fun(Date d) Fun(Date())调用了1次构造,一次析构,编译器自动优化,将两步合二为一

只有在一个表达式中,编译器才优化

Date fun2()

{

            Date d;                 Date d;

            return d;                d=fun2();

}                    2次构造-->1次拷贝构造-->1次析构-->1次赋值-->2次析构

 

Date d=fun2(); 这样的表达式编译器会优化   1次构造-->1次拷贝构造-->2次析构

如果以上语句,函数内部return Date()  1次构造-->1次析构

3.接收返回值

优化:两次拷贝构造合成一次

写了拷贝构造不写构造函数,会报错

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值