C++学习笔记(三)——函数重载、封装、对象特性

写在前面

写本系列的目的(自用)是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。以前三天打鱼两天晒网地学习C++,一直无法对C++熟练掌握,核心的面向对象编程并不熟悉,STL语法只会部分,因此也希望自己未来能做到熟悉甚至精通C++。

Part 2 面向对象编程

该部分主要通过b站黑马程序员的视频来进行学习,记录笔记。

一、函数重载

在介绍函数重载前需要补充两个前置知识,函数的默认参数及函数的占位参数。

1、函数的默认参数

函数的默认参数在前面函数部分有简单提到过,在C++中,函数的形参列表可以有默认值。

int func(int a, int b = 10)    //这里的int b = 10就是函数的默认参数
{
    return a+b;
}

int main()
{
    int a = 5, b = 20;

    int c = func(a);         //仅传入一个参数时,剩下的参数选用默认参数,c = 5 + 10 = 15
    int d = func(a, b);      //传入两个参数,则以传入的参数为主,d = 5 + 20 = 25

    cout<< c << " "<< d <<end;   //可以验证一下

    return 0;
}

2、函数的占位参数

C++的形参列表可以有占位参数,调用函数时必须填补该位置。

void func(int a, int)        //这里单独的那个int就是占位参数,只写数据类型
{                                   
     cout<< "func" << endl;
}

void func2(int a, int = 10)  //此外,占位参数也可以有默认参数
{
    cout<< "func2" <<end;
}

int main()
{
    fun(10, 10);            //占位参数必须填补,函数才能正常调用
                            //占位参数就好比在自习室,需要去卫生间时,放个书包在位置上占位
                            //只不过C++里放置的是参数类型
    func2(10);              //这是有默认参数的情况

    return 0;
}

3、函数重载

函数重载需要满足以下三个条件:

1、在同一个作用域下

2、函数名称相同

3、函数参数 类型不同 或 个数不同 或 顺序不同

注:函数的返回值不能作为函数重载的条件

函数重载使得函数名可以相同,提高函数的复用性。

//目前的代码都在全局作用域中,涉及到类后会出现其他作用域
void func()                   //函数重载其实只需要你从编译器的角度去考虑
{
    cout<< "func()"<<endl;
}

/*
void func()                   //例如此处我注释的这段代码,如果解开注释,运行时就会出错
{                             //两个完全相同的函数并不能满足函数重载
    cout<< "func()!"<<end;    //在调用func()时,编译器不知道调哪个函数,哪怕函数体内不同也不行
}
*/

void func(int a)              //这个函数(第二个函数)和第一个函数的参数个数不同
{
    cout<< "func(int)"<<endl;
}

void func(double a)           //第三个和第二个函数类型不同
{
    cout<< "func(double)"<<endl; 
}

void func(int a, double b)               //以下是4,5两个函数,两个函数参数顺序不同
{
    cout<< "func(int, double)"<<endl; 
}

void func(double b, int a)
{
    cout<< "func(double, int)"<<endl; 
}

/*
int func(double b, int a)                //注,注释内的函数和第五个函数仅是返回值不同
{                                        //此时也不满足函数重载条件
    cout<< "func(double, int)"<<endl;    //从编译器的角度考虑,调用函数时使用func(3.14, 10);
}                                        //无法从返回值的角度区分
*/


int main()
{
    func();                   //会调用第一个函数
    func(10);                 //会调用第二个函数
    func(3.14);               //会调用第三个函数
    func(10, 3.14);           //会调用第四个函数
    func(3.14, 10);           //会调用第五个函数

    return 0;
}

其实在介绍完函数重载后,就知道占位参数的作用了,占位参数可以在函数重载中使用,保证函数参数不同等。此外,还需要补充函数重载和引用、函数重载和默认参数

//函数重载和引用
void func(int &a)                       //这两个函数可以满足函数重载
{                                       //加上const前后是两个不同类型
    cout<< "func(int &a)"<<endl;
}

void func(const int &a)
{
    cout<< "func(const int &a)"<<endl;
}

int main()
{   
    int a = 10;
    func(a);                   //会调用第一个函数
    //对于这个函数,需要注意的是不能传入func(10), 传入会变为int &a = 10, 所以需要传入变量a

    func(10);                  //会调用第二个函数,可以将传入参数带入来进行理解
    //传入10时,对于第一个函数而言,时int &a = 10,我们知道引用是别名,之前介绍过引用=常数,不合法
    //对于第二个函数, const int &a = 10 合法

    return 0;
}
//函数重载和默认参数
void func(int a, int b = 10)
{                                       
    cout<< "func(int a, int b = 10)"<<endl;
}


void func(int a)
{                                       
    cout<< "func(int a)"<<endl;
}



int main()
{   
    func(10);           //注意,此时会报错,从编译器的角度来考虑。对于函数1,传入func(10)可以运行
    return 0;           //此时会使用b的默认值。对于函数2,原本就只需要传入一个参数,也可以运行
}                       //因此编译器区分不了,会报错

二、类和对象

现在就正式进入面向对象编程了,这是C++中最重要的内容!

C++面向对象的三大特性为:封装、继承、多态

C++中万事万物都皆为对象,且对象上有其属性和行为

例如:人可以作为一个具体的对象,人的属性可以有姓名、年龄、身高、体重等,行为可以有跑、跳等。在游戏开发过程中就经常使用类来表示人,包括姓名、职业、攻击方式,受击反馈等。

而具有相同性质的对象,就可以抽象为类,人属于人类,车属于车类。具体的对象就是类的实例化。

1、封装

封装的意义:

1、将属性行为作为一个整体,表现生活中的事物

2、将属性和行为加以权限控制

此外,需要知道的是,类中的属性和行为,我们统一称之为成员;

属性又可称为 成员属性 和 成员变量 ;行为又可称之为 成员函数 和 成员方法。

(1)属性和行为作为一个整体

以下是一个简单的类的示例,主要体现封装的第一种意义

const double PI = 3.14; 

class Circle                        //这是一个圆类,可以看到,圆的属性和行为都封装在这个类里
{
public:                             //代表是公共权限,关于权限的内容后续会介绍
    
    int m_r;                        //圆的半径,这是类中的属性
    
    double calculateZC()            //打印圆的周长,这是类中的行为
    {
        return 2 * PI * m_r;
    }
};

int main()
{
    Circle c1;                     //通过圆类创建一个具体对象,又叫实例化
    c1.m_r = 10;                   //为其属性赋值
    cout<< "周长为" << c1.calculateZC() <<endl;        //调用类行为
    
    system("pause");               //这个命令的主要目的是在程序执行完毕后保持命令行窗口打开,以    
                                   //便用户能够查看程序的输出结果或任何错误消息            
    return 0; 
}

同样的,在类中,除了像上述例子一样为其属性赋值,还可以通过行为为其赋值

const double PI = 3.14; 

class Circle                        
{
public:                             //代表是公共权限,关于权限的内容后续会介绍
    
    int m_r;

    void setR(int r)                        //为圆的半径赋值
    {
        m_r = r;
    }
        
    double calculateZC()            //打印圆的周长
    {
        return 2 * PI * m_r;
    }
};

int main()
{
    Circle c1;                     //通过圆类创建一个具体对象,又叫实例化
    c1.setR(10);                   //为其属性赋值
    cout<< "周长为" << c1.calculateZC() <<endl;        //调用类行为
    
    system("pause");                   
           
    return 0; 
}
(2)权限控制

下面介绍封装的第二种意义,权限控制

类在设计时,可以把属性和行为放在不同的权限下,加以控制。访问权限有三种:

public        公共权限 :类内可以访问  类外可以访问
protected  保护权限 :类内可以访问  类外不可以访问 儿子可以访问
private       私有权限 :类内可以访问  类外不可以访问  儿子不可以访问

class Person
{
public:                    //公共权限
    string m_Name;

protected:                 //保护权限
    string m_Car;

private:                   //私有权限
    int m_Password;

public:                    //公共权限
    void func()     
    {                  
        m_Name = "张三";        
        m_Car = "拖拉机";              //注意,这是在类内部访问私有权限和保护权限
        m_Password = 123456;          //可以正常访问
    }
};


int main()
{
    Person p1;

    p1.m_Name = "李四";         //可以正常执行并修改
    //p1.m_Car = "奔驰";        //这是保护权限内容,在类外不能访问
    //p1.m_Password = 1234;    //这是私有权限内容,在类外不能访问    

    p1.func();                 //可以正常调用,因为调用的函数属于公共部分,借助公共部分的函数
                               //在类的内部访问类的私有及保护部分
}

在这里可以谈一下struct和class的区别了。它们唯一的区别在于访问权限不同。struct默认为公共,class默认为私有。这也是在以上代码中经常会写public的原因。

(3)将成员属性设置为私有

 将成员属性设置为私有,可以巧妙地控制类内成员的读写权限。

class Person
{
public:
    void setName(string name)      //写姓名部分
    {
        m_Name = name;
    }

    string getName()               //读姓名部分
    {
        return m_Name;
    }

    string getAge()                //读年龄部分,由于年龄权限为只读,因此只有一个函数
    {
        return m_Age;
    }

    void setIdol(string idol)      //写偶像部分,由于偶像权限为只写,因此只有一个函数
    {
        m_Idol = idol;
    }

private:                   //均设置为私有部分,通过公共部分的函数来巧妙实现权限控制

    string m_Name;         //可读可写
    
    int m_Age = 18;        //只读  
 
    string m_Idol;         //只写
}

int main()
{
    Person p;
    p.setName("张三");
    cout << "姓名是:" << p.getName() <<endl;
    cout << "年龄是:" << p.getAge() <<endl;
    p.setIdol("小明");
    
    return 0;
}

2、对象特性

C++中的面向对象来源于生活,每个对象也都会有 初始设置 以及 对象销毁前的清理数据的设置。

一个对象或者变量没有初始状态,对其使用后果是未知。同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。

(1)构造函数和析构函数

C++使用构造函数和析构函数来分别进行对象的初始化和对象的清理。如果用户没有编写这两个函数,那么这两个函数都是由系统默认实现,只是系统实现的函数是空实现:

class Example
{
public:
    Example(){  }       //构造函数
    ~Example(){  }      //析构函数
}

构造函数:创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。语法为:

1、没有返回值也不写void

2、函数名称与类名相同

3、构造函数可以有参数,因此可以发生重载

4、程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数:对象销毁前系统自动调用,执行一些清理工作。语法为:

1、没有返回值也不写void

2、函数名称与类名相同,在名称前加上符号  ~

3、析构函数不可以有参数,因此不可以发生重载

4、程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

下面是自己编写析构函数时,系统调用用户编写的函数

class Person
{
public:
    Person()                         //构造函数
    {
         cout<<"构造函数"<<endl;
    }
 
    ~Person()                       //析构函数
    {
        cout<<"析构函数"<<endl;
    }
};

void test()
{
    Person p;                       //该函数中创建一个局部变量,在函数调用开始时创建,函数调用
}                                   //结束时销毁

int main()
{
    test();                         //因此在执行这条语句时析构函数和构造函数都会调用,输出两句

    Person p1;                      //该变量会在main函数结束时销毁
                                    //因此此时调用只会看到构造函数
    system("pause");                //在界面中按下“enter”后能在窗口关闭的一瞬间看到析构函数
    return 0;
}
(2)构造函数的分类及调用

1、构造函数有两种分类方式:

按参数分为: 有参构造和无参构造

按类型分为: 普通构造和拷贝构造

2、构造函数有三种调用方式:括号法、显示法、隐式转换法

这里说一下我对构造函数的理解:构造函数就是你选用的一种初始化你的对象的统一方式。

在默认情况下系统进行调用空构造,此时不对你的对象来进行统一初始化。

如果采用有参构造,那么就是你指定用某参数来对创建的对象的某属性进行初始化。

如果采用拷贝构造,那么就是你希望新的对象能够和某个已有对象属性相同,以此来进行拷贝初始化。

下面看案例:

class Person
{
private:
    int age;

public:
    Person()                         //无参构造函数,又称默认构造,也是普通构造函数
    {
         cout<<"无参构造函数"<<endl;
    }

    Person(int a)                    //有参构造函数,也是普通构造函数
    {
         age = a;                    //这里给属性age赋值
         cout<<"有参构造函数"<<endl;
    }

    Person(const Person &p)         //拷贝构造函数,也是有参构造函数
    {
         age = p.age;               //这里将已有对象p赋值给新对象  
         cout<<"拷贝构造函数"<<endl; //要注意必须用const,防止对已有对象进行修改
    }
 
    ~Person()                       //析构函数
    {
        cout<<"析构函数"<<endl;
    }
};

void test()
{
    //括号法 
    Person p1;      //默认构造函数调用,会调用第一个构造函数
                    //此外,需要注意的是,调用默认构造函数,不要加()    
                    //因为若写成Person p1();  编译器会认为这是一个函数声明,而非创建对象 
    Person p2(10);  //有参构造函数调用,会调用第二个构造函数
    Person p3(p2);  //拷贝构造函数调用,会调用第三个构造函数

    cout<<"p2的年龄是:"<<p2.age<<endl;       //在这里进行验证,可以发现p2的年龄是10
    cout<<"p3的年龄是:"<<p3.age<<endl;       //p3是p2的拷贝,p3的年龄也是10

    //显示法
    Person p4;                 //默认函数构造
    Person p5 = Person(10);    //有参构造       
    Person p6 = Person(p5);    //拷贝构造
    //显示法会让人想到 int a = add(10, 5); 这样的语法,这里假设你已经书写了add函数
    //我们也知道,如果单独写add(10, 5), add函数的返回值没有变量接收,系统会立即回收这部分内存

    Person(10);                //那么这么写也同理,这是一个匿名对象,当这行执行完后
                               //系统会立即回收匿名对象的内存空间
    //Person(p3);              //需要补充的是,不要用拷贝函数写匿名对象
                               //编译器会认为 Person(p3) 等价于 Person p3

   //隐式转换法
   Person p7 = 10;             //相当于Person p7 = Person(10),有参构造
   Person p8 = p7;             //相当于Person p8 = Person(p7),拷贝构造
}                                   

int main()
{
    test();                                            
                                    
    system("pause");                
    return 0;
}

篇幅问题接下来的内容写在下一篇笔记中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值