C++(2)重载,默认值,面向对象,构造函数

164 阅读2分钟

函数重载

概念

C++中函数名相同,形参列表不同的函数构成重载关系,C语言不支持,所谓形参列表不同:

参数个数不同
参数类型不同
参数顺序不同
​
void print();
void print(int);
void print(double);
void print(int,double);
void print(double,int);

重载函数的调用

调用重载函数时,根据传递的实参来选择合适的函数调用。

原理:

C++编译函数时不但记录函数名,还会记录参数列表
函数调用时不但匹配函数名,还匹配参数列表

函数重载的意义

对于类似功能,在不同的数据下可以用一个函数名实现,大大简化了上层调用的复杂度。

练习:

使用函数重载实现两个数的加法(add),两个数可以使两个整数,两个双精度浮点数,也可以是一个双精度浮点数和一个整数。

哑元

如果一个形参只有类型,没有形参名,这种参数就叫哑元

int print(int/*哑元*/)
{
    //......    
}

//哑元的作用就是实现参数没有区别的函数的重载

Train

/*01-函数重载*/
#include <iostream>

using namespace std;

//以下函数构成重载关系
void print()
{
    cout<<"print()"<<endl;
}

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


void print(double a)
{
    cout<<"print(double)"<<endl;
}

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

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

int main()
{
    print();
    print(1);
    print(3.15);
    print(2,4.6);
    print(1.5,7);

    //print(12,23);

    return 0;
}
/*02-加法函数的重载*/
#include <iostream>

using namespace std;

int add(int a,int b)
{
    cout<<"add(int,int)"<<endl;
    return a+b;
}

double add(double a,double b)
{
    cout<<"add(double,double)"<<endl;
    return a+b;
}

double add(int a,double b)
{
    cout<<"add(int,double)"<<endl;
    return a+b;
}

double add(double a,int b)
{
    cout<<"add(double,int)"<<endl;
    return a+b;
}


int main()
{
    cout<<add(1,2)<<endl;
    cout<<add(1.1,2)<<endl;
    cout<<add(1.1,2.2)<<endl;
    cout<<add(1,2.2)<<endl;

    return 0;
}

函数参数的默认值

概念

函数参数的默认值指的是在函数调用时,如果不提供对应的实参,就选择使用参数的默认值,如果传递了实参,就使用实参覆盖默认值。

语法

返回值类型 函数名(形参类型1 形参名1,...,形参类型n 形参名n=默认值)
{
    //......
}

1.有默认值的参数必须靠右,一个参数有默认值,其右边的参数必须有默认值
    void xxx(int a=10,int b);//错误
2.一个形参有默认值,调用时不提供对应的实参就使用默认值,提供了实参就覆盖默认值
3.使用参数默认值时需要注意不要和函数重载相冲突
    int add(int a,int b);
    int add(int a,int b,int c=100);//冲突
4.如果有函数声明,参数的默认值必须写到声明中

函数参数的默认值的意义

减少传参的个数,提高函数调用的效率
方便函数的调用

template:

/*04-函数参数的默认值*/
#include <iostream>

using namespace std;

//函数参数的默认值必须 写在声明中
int add(int x=100,int y=200,int z=300);

int main()
{
    cout<<add(1,2,3)<<endl;
    cout<<add(1,2)<<endl;
    cout<<add(1)<<endl;
    cout<<add()<<endl;

    return 0;
}

int add(int x,int y,int z)
{
    return x+y+z;
}

哑元

/*03-哑元*/
#include <iostream>

using namespace std;

void print()
{
    cout<<"print()"<<endl;
}

void print(int/*哑元*/)
{
    cout<<"print(int)"<<endl;
}

int main()
{
    print();
    print(1);

    return 0;
}

练习:

设计一个打印整数数组的函数,默认打印前5个元素,默认使用逗号作为分隔符,也可以指定打印个数和分隔符

/*05-打印数组*/
#include <iostream>

using namespace std;

void print_arr(int *a,int n=5,char op=',')
{
    //打印到倒数第二个
    for(int i=0;i<n-1;i++)
        cout<<a[i]<<op;

    //打印最后一个
    cout<<a[n-1]<<endl;
}

int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9};

    print_arr(arr);
    print_arr(arr,7);
    print_arr(arr,3,' ');
    print_arr(arr,7,'-');

    return 0;
}
int arr[] = {1,2,3,4,5,6,7,8,9};

默认打印 ----- 1,2,3,4,5
打印7个,使用-作为分格符 ------ 1-2-3-4-5-6-7

提示:传三个参数:数组首地址,打印长度和分隔符,打印长度和分隔符使用默认值

面向对象

概念

一切皆对象,一个程序就是一组对象组成的整体,程序的功能由对象之间相互传递消息来实现的。

image-20220717210734561

C++中对象都有类型,同一种类型的对象具有相同的属性和功能。

如何描述对象

抽取描述对象的感兴趣的共同特征

image-20220717210913186

对象的共同特征由属性和功能组成

使用变量描述共同的属性
使用函数描述共同的功能

如何在C++中描述对象的类型

使用结构体(struct)描述

C++对结构体的语法进行了扩展,结构体中不但可以有成员变量,也可以有成员函数。因此结构体可以用来描述对象的类型,在C++中,对象的类型叫类。

//使用结构体描述时间对象
属性:
    时
    分
    秒
功能:
    设置时间
    获取时间
    走秒
   ....

使用类(class)描述对象的类型

class也可以用来描述对象的类型,语法和struct几乎一致

类中的成员具有访问属性,访问属性分为三类:

访问属性

public --- 公有属性 可以在类外和类内访问该成员

protected --- 保护属性 可以在类内部和子类中访问该成员

private --- 私有属性 只能在类内部访问

类成员的访问属性就是实现封装的基础,封装意思就是私有的数据隐藏,对外的接口公开。struct中的成员的默认访问属性是公开的,class中的成员的默认访问属性是私有的。

在实际开发中,应该显示指定成员的访问属性:

class 类名{
public:
    公有成员;
protected:
    保护成员;
private:
    私有成员            
};

//一个类中同个访问属性可以多次出现
//通常把公开的成员写在类的最前面,私有成员写在类的最后

注意:

一般来说,成员变量总是设置为私有属性,如果类外需要访问就提供公开的接口成员函数。

在C++不推荐使用struct,而应该使用class

练习:

设计一个Animal的类,包含name(char [20]),age(int)的私有成员,weight(float)的保护成员,提供show(打印信息),设置name,设置age的对外接口(公开)。

/*08-构造Animal类*/
#include <iostream>
#include <cstring>

using namespace std;

class Animal {
public:

    void show() {
        cout << name << ":" << age << ":" << weight << endl;
    }

    //设置name
    void set_name(const char *s) {
        strcpy(name, s);
    }

    //设置age
    void set_age(int a) {
        age = a;
    }

    //设置体重
    void set_weight(float w) {
        weight = w;
    }

protected:
    float weight;
private:
    int age;
    char name[20];
};

int main() {
    Animal an;//调用无参构造函数
    an.set_name("小飞飞");
    an.set_age(4);
    an.set_weight(14.4f);
    an.show();

    return 0;
}

构造函数

概念

构造函数的作用是初始化对象,处于类内部

构造函数是一个特殊的函数,函数名和类名相同,并且没有返回值

构造函数在创建对象时自动调用一次。

构造函数必须是公有的访问属性。

如果类没有构造函数,编译器会自动生成一个什么也不干的构造函数,如果类中实现了构造函数,编译器就不会做这个工作。

类应该实现构造函数。

构造函数参数的传递

如果构造函数有参数,需要在构造对象铜锅小括号传递实参

Animal an(构造函数的实参);
Animal *pa = new Animal(构造函数的实参);

构造函数的重载和参数默认值

一个类可以有多个构造函数,这些构造函数构成重载关系,在构造对象时选择合适的构造函数去调用。构造函数也可以由参数默认值,但是注意不要和重载冲突。

对象的构造过程

1.系统根据对象大小分配内存空间
2.检查成员变量的类型,如果是基本类型就什么都不做,如果是类类型调用该类的构造函数

初始化参数列表

类中有引用成员,const成员必须在调用构造函数前初始化。

在构造函数的形参列表之后,函数语句体之前,使用初始化参数列表可以在调用构造函数之前对成员进行初始化,语法如下:

class A{
public:
    A(...):/*初始化参数列表*/{
        //...
    }    
};

//初始化参数列表
构造函数(形参列表):初始成员1(值1),初始化成员2(值2),...{
    函数语句体;
}

//所有的成员都可以使用初始化参数列表来初始化

练习:

为Time类提供构造函数,默认初始化为23:59:55,也可以传入时间初始化,使用初始化参数列表

作业:

实现一个mystring类,用于存储字符串(成员 ===> char *指针,int空间大小),存储使用堆内存,提供构造函数,默认构造空间大小为10的空串。

提供一个打印字符串内容的成员函数

提供一个获取空间长度的成员函数

提供一个修改字符串内容的成员函数(扩容)

/*01-字符串类的作业*/
#include <iostream>
#include <cstring>
​
using namespace std;
​
class mystring {
public:
    //构造函数
    mystring(const char *s = NULL) {
        if (!s) {//没有传参数
            len = 10;
            str = new char[len];
            memset(str, 0, sizeof(len));
        } else {
            len = strlen(s) + 1;  //这里为什么要加上去
            str = new char[len];
            strcpy(str, s);
        }
    }
​
    //打印字符串
    void show() {
        cout << str << endl;
    }
​
    //获取空间大小
    size_t get_len() {
​
        cout << "this(指向构造的对象)" << this << endl;
​
        //使用了该函数构造函数的地址
        return len;
    }
​
    //修改字符串内容 健壮性
    void modify_str(const char *s) {
        if (!s) {//为空修改为长度为10的空串
            delete[] str;
            len = 10;
            str = new char[len];
            memset(str, 0, sizeof(len));
        } else {//非空
            if (len < strlen(s) + 1) {//空间不够
                //调整空间
                delete[] str;
                len = strlen(s) + 1;
                str = new char[len];
            }
            strcpy(str, s);
        }
    }
​
private:
    char *str;//字符串内容首地址
    size_t len;//空间大小
};
​
​
int main() {
    //mystring str1;
    mystring str1("hello");
    cout << "str1地址:" << &str1 << endl;
    str1.show();
    cout << str1.get_len() << endl;
​
    str1.modify_str("welcome to GEC!");
    str1.show();
    cout << str1.get_len() << endl;
​
    mystring str2;
    cout << "str2地址:" << &str2 << endl;
    cout << str2.get_len() << endl;
​
    return 0;
}