C++基础面向对象特征

目录

学习目标:

学习内容:

1. C++对函数的扩充

1.1 函数重载(overload)

1.1.1 概念

1.1.2  要求

1.2 函数的默认参数 

1.3 哑元 

1.4 内联函数

        内联函数与带参宏的区别 (重要)

2. 封装

2.1 面向对象的三大特质

2.2 C++中的类(class)

2.3 定义格式

2.4 this指针 

2.5 类的大小 

3. 类中特殊的成员函数(非常重要) 

3.1 类中提供的特殊成员函数

3.2 构造函数

3.3 析构函数 

课外作业:

仿照string类,实现myString


学习目标:


学习内容:

1. C++对函数的扩充

1.1 函数重载(overload)

1.1.1 概念

        函数重载就是能够实现"一名多用",是实现泛型编程的一种

        泛型编程:试图以不变的代码,来实现可变的功能

        C++引入了函数重载:运行在同一个作用域下,定义多个同名的函数。但是,要求这些同名的函数必须参数参数不同,后期调用时,系统会根据主调函数的实参去自动匹配相应的函数执行。

1.1.2  要求

        函数名相同

        形参列表必须不同

        返回值无关

        必须在同一个作用域下

#include <iostream>


using namespace std;


//定义两个整数的求和
int sum(int m, int n)
{
    return m+n;
}


//下面这个函数会报错,即使函数返回值和函数体内容不同,也不能构成重载
//void sum(int m, int n)
//{
//    return m-n;
//}


//定义两个小数的求和
float sum(float m, float n)
{
    return  m+n;
}


//定义两个double类型的数据求和
double sum(double m, double n)
{
    return m+n;
}


//定义三个整数的求和
int sum(int m, double n, int k)
{
    return m+n+k;
}


int main()
{
    cout<<sum(3,7)<<endl;        //10




    cout<<sum(3.5,7.5)<<endl;        //11


    cout<<sum(3,7.5, 6)<<endl;   //16


    return 0;
}

1.2 函数的默认参数 

        1. C++允许在定义函数时,给其中的某几个参数设置默认参数,对于设置了默认值的参数,主调函数传数据就使用主调函数传的,主调函数不传数据,就使用默认值

        2. 默认参数的设置原则:靠右原则,也就是说必须当前参数的右边的形参设置了默认值后,当前参数才能设置默认值。

        原因是,函数参数传递的过程是靠左原则,实参向形参传递时,向给左侧的形参赋值

        3. 当函数重载和默认参数同时出现时,注意,如果带默认参数的函数包含了重载的函数,那么函数定义时没有问题,但是函数版调用时会不确定调用哪一个

        4. 如果带默认参数的函数,声明和定义分开时,默认参数写在声明部分,定义部分就不写默认参数了

#include <iostream>


using namespace std;


int sum(int  = 0, int =0, int =100);    //函数声明


//定义重载函数时
//int sum(int x, int y):如果有默认参数包含了该函数,函数定义是没有问题,但是函数调用时会不知道调用哪一个
int sum(int x, double y)
{
    return x+y;
}




int main()
{
    cout << sum(2,3,5) << endl;       //调用了默认参数的函数,三个参数主调函数传递


    cout << sum(2,3) << endl;       //调用了默认参数的函数,前两个参数主调函数传递,后一个参数使用默认的


    cout << sum(2) << endl;       //调用了默认参数的函数,前一个参数主调函数传递,后两个参数使用默认的


    cout << sum() << endl;        //调用了默认参数的函数,三个参数使用的都是默认值


    return 0;
}


//函数定义在被调函数后面
int sum(int m , int n, int k)
{
    return m+n+k;
}

1.3 哑元 

        1> C++定义函数时,支持哑元,运行某个形参或某几个形参只有声明,没有实际意义,唯一的作用就是起到占位作用

#include <iostream>


using namespace std;


//该函数中的参数2就是一个哑元,只起到占位作用,不让程序优化后报错
int sum(int m, int, int k)
{
    return m+k;
}






int main()
{
    cout << sum(2,3,5) << endl;
    cout << sum(2,3,5) << endl;

    return 0;
}

        2> 使用场景1:当某个程序已经发布后,随着技术的升级,可能会对某些函数进行优化,将原本有多个参数的函数,只需要少了的参数就可以完成,但是此时,未优化前的函数已经被多次调用,修改起来不方便,此时就可以在定义函数时,将被优化掉的形参设置成哑元,只起到接受数据的作用,并不起实际作用

        3> 使用场景3:后期学习自增自减运算符重载时,用于区分前置和后置

1.4 内联函数

        1> C++支持内联函数,使用关键字inline在函数定义前使用

        2> 作用:被设置成内联函数的函数,在编译时,编译器会建议将内联函数自动展开,无需在运行时为该函数开辟内存空间,提高程序的执行效率

        3> 内联函数的设置要求:

                1、函数体积要小

                2、调用比较频繁的函数

                3、递归函数不能设置成内联函数

                4、不足:如果大量使用内联函数,会使得主程序体积膨胀

#include <iostream>


using namespace std;


//该函数中的参数2就是一个哑元,只起到占位作用,不让程序优化后报错
inline int sum(int m, int, int k)
{
    return m+k;
}

int main()
{
    cout << sum(2,3,5) << endl;
    cout << sum(2,3,5) << endl;

    return 0;
}

        内联函数与带参宏的区别 (重要)

在C++中,内联函数和带参宏虽然都可以用来在编译时展开代码以减少函数调用的开销,但它们之间存在几个关键的区别:

1. 类型检查
内联函数:内联函数是真正的函数,支持类型安全,会进行类型检查。
带参宏:宏只是预处理器的文本替换工具,不进行类型检查,容易引发类型相关的错误。

2. 编译器优化
内联函数:内联函数允许编译器进行更多优化,比如常量折叠、死代码消除等。
带参宏:宏展开后的代码通常不会受到这些优化的好处,因为它们在预处理阶段就已经被处理了。

3. 调试
内联函数:在调试时,内联函数可以像普通函数一样进行单步调试。
带参宏:宏在预处理阶段就已经被展开,这使得调试变得困难,因为它们在源代码中不再是独立的实体。

4. 作用域
内联函数:内联函数遵循正常的作用域规则。
带参宏:宏没有作用域的概念,它们可以在定义后的任何地方被展开,有时会导致意外的名字冲突。

5. 重复代码
内联函数:内联函数的代码在多个调用点展开时,编译器可以智能地处理,避免不必要的代码膨胀。
带参宏:宏每次使用时都会文本上的复制粘贴,可能会导致代码膨胀。

2. 封装

2.1 面向对象的三大特质

        封装、继承、多态,如果问有四大特征,可以外加一个抽象

                封装:将实现同一事物的所有的属性(成员变量)和行为(成员函数)封装到一个整体,我们称之为类(class)。类对外部信息有一个屏障作用。

2.2 C++中的类(class)

        C++中的类,是由C++中的结构体演化而来的,只需要将struct改成关键字class,就定义了一个类

        C++中类和结构体的区别:默认的权限不同,结构体中默认权限为public,类中默认权限为private

        默认的继承方式不同,结构体的默认继承方式为public,类的默认继承方式为private

2.3 定义格式

class 类名 { public: 功能的成员属性、函数 protected: 受保护的成员属性、函数 private: 私有的成员属性、函数 };

        1> 类中的成员属性和成员函数分为不同的权限

                public:该权限下的成员,可以在类内、子类中、类外被访问

                protected:该权限下的成员,可以在类内、子类中直接被访问,类外不允许被访问

                private:该权限下的成员,只能在类内被访问,子类、类外不允许被访问

        2> 如果没有指定权限,则默认为私有权限

        3> 一个类中,访问权限可以出现多次,也可以出现在任意地方,一般情况下,我们将同一权限下的成员写到一个关键字下面

        4> 一个访问权限的范围,是从当前关键字开始到下一个关键字或者整个类的定义结束为止

        5> 一般情况下,成员属性定义成私有的,成员函数定义成公有的

        6> 类的访问权限是针对于类体而言的,不是针对于类对象的

#include <iostream>


using namespace std;


//将实现人的所有属性和行为都封装到一个整体,
//后期如果需要一个人,只需要使用该类实例化对象后,通过对象为整个程序服务
class Person
{
    string skill = "C++";          //技能


public:
    string name = "zhangsan";        //姓名    功能的属性


protected:
    int pwd = 123456;           //银行卡密码


private:
    int money = 1000000;         //私房钱


public:
    void show()
    {
        cout<<"  name = "<<name<<endl;        //公共权限下的成员,类内可以直接访问
        cout<<"  pwd = "<<pwd<<endl;          //受保护权限下的成员,类内可以直接访问
        cout<<"  money = "<<money<<endl;      //私有成员,类内可以直接访问
        cout<<"  skill = "<<skill<<endl;      //默认为私有成员,类内可以直接访问
    }


};


/******************************************主程序***************************/
int main()
{
    Person p1;          //使用类定义变量的过程,称为实例化对象,p1就是Person类的一个对象


    //通过p1使用成员
    cout<<"name = "<<p1.name<<endl;          //公共权限下的成员,类外可以直接访问
    //cout<<"pwd = "<<p1.pwd<<endl;            //收保护成员,类外无法直接访问
    //cout<<"money = "<<p1.money<<endl;      //私有成员,类内可以直接访问,类外无法访问
    //cout<<"skill = "<<p1.skill<<endl;      //默认为私有成员,类内可以直接访问,类外无法访问
    p1.show();                                 //类中公共权限下的成员,类外可以直接访问


    return 0;
}

2.4 this指针 

        1> this的内含:是类的非静态成员函数所拥有的一个隐藏的形参指针,指代该对象本身起始地址,哪个对象使用我,我就表示哪个对象

        2> this指针的格式:类名 * const this;

        3> 对于this指针而言,如果没有显性使用该指针,仅仅只是使用成员,那也是默认使用了this指针

        4> this指针的使用场景

                场景1:当成员函数的形参名和成员变量同名时,可以使用this指针进行区分

                场景2:在拷贝复制函数中,需要返回自身引用时,也必须使用this指针(后面讲)

#include <iostream>


using namespace std;
class rectangle
{
public:
    void init(int width, int height);       //初始化一个矩形

    void show()
    {
        cout<<"width = "<<width<<"  height = "<<height<<endl;
    }
private:
    int width=0;             //矩形宽度
    int height=0;             //矩形高度
};


void rectangle::init(int width, int height)
{
    if(width<=0 || height<=0)
    {
        cout<<"初始化失败"<<endl;
    }
    this->width = width;             //可以通过this指针改变this所指向内存中的值
    this->height = height;


    cout<<"this = "<<this<<endl;


    //this = NULL;          //不能更改指针的指向


}


int main()
{
    //使用矩形类实例化一个矩形
    rectangle r1;            //实例化一个对象
    r1.init(3,5);          //初始化一个矩形
    cout<<"&r1 = "<<&r1<<endl;         //r1的地址
    r1.show();

    //在实例化一个对象
    rectangle r2;
    r2.init(2,2);
    cout<<"&r2 = "<<&r2<<endl;         //r2的地址

    return 0;
}

2.5 类的大小 

        1> 一个空的类的大小为1字节,用于占位使用,如果后期有成员变量的加入,则会将这一字节的空间分配出去

        2> 类中的成员函数不占类的空间大小,但是成员属性会占用类的大小

        3> 成员属性分配空间的大小遵循字节对齐原则

        4> 如果类中有虚函数(后期讲),那么类中就会多一个虚指针的大小

        5> 如果该类是虚继承(后期讲)自父类,那么该类中也会增加一个虚指针的大小

#include <iostream>


using namespace std;


class Temp
{
public:
    int num;      //整型成员变量


    void show()
    {
        cout<<"num = "<<num<<endl;
    }


    //类中有虚函数时,就会多分配一个虚指针的空间
    virtual void display()
    {
        cout<<"我是虚函数"<<endl;
    }


    virtual void display(int )
    {
        cout<<"我是虚函数"<<endl;
    }


};


int main()
{
    cout<<"sizeof(Temp) = "<<sizeof(Temp)<<endl;

    return 0;
}

3. 类中特殊的成员函数(非常重要) 

3.1 类中提供的特殊成员函数

        1> 特殊的原因:如果用户不显性定义这些函数,系统也会自动提供这些函数,如果用户显性定义了这些函数,那么系统就不提供了。

        无论这些特殊的成员函数,是系统提供的还是用户字节定义的,都无需用户手动调用,特殊的时机,系统会自动调用

        2> 构造函数、析构函数、拷贝构造函数、拷贝复制函数、移动构造函数、移动复值函数、取地址运算符重载

3.2 构造函数

        1> 功能:使用类去实例化对象时,为对象进行资源的申请以及初始化使用的

        2> 定义格式

                1、构造函数没有返回值

                2、函数名与类同名

                3、访问权限,一般为public

                4、 类名(形参列表){函数体内容}

        3> 调用时机:当使用类实例化对象时,系统自动调用,无需手动调用

                1、栈区空间:使用类实例化对象时,系统自动调用

                        类名 变量名(实参); //此时就调用的构造函数

                2、堆区空间:只有在使用new申请空间时,自动调用构造函数

                        类名 *指针名; //此时仅仅只是定义一个指针变量,并没有为对象分配空间

                        指针名 = new 类名(实参); //此时调用的构造函数

#include <iostream>


using namespace std;


class Stu
{
public:
    Stu()
    {
        cout<<"Stu的构造函数"<<endl;
    }
};




int main()
{
    //在栈区申请对象空间
    Stu s1;


    //堆区空间申请对象
    Stu *ptr;          //此时不调用构造函数
    ptr = new Stu;      //此时调用构造函数


    return 0;
}

        4> 构造函数分为有参构造和无参构造函数,有参构造函数可以为成员属性进行初始化,这些构造函数之间构成重载关系

        5> 一个类中可以有多个构造函数,每个对象仅仅只会调用一个构造函数

        6> 如果类中没有定义构造函数,那么,系统会自动提供一个无参构造函数,用于对对象空间的申请,如果程序员自己定义了构造函数,系统就不再提供那个无参构造了,如果还想使用无参构造,需要自己定义一个无参构造

 

#include <iostream>


using namespace std;


class Stu
{
private:
    string name;
    int age;
    double score;


public:
    //无参构造
    Stu()
    {
        cout<<"Stu的无参构造函数"<<endl;
    }
    //自定义有参构造
    Stu(string n, int a)
    {
        this->name = n;
        this->age = a;
        cout<<"Stu::有参构造"<<endl;
    }


    void show()
    {
        cout<<"name = "<<name<<endl;
        cout<<"age = "<<age<<endl;
        cout<<"score = "<<score<<endl;
    }
};




int main()
{
    //在栈区申请对象空间
    Stu s1;                       //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
    s1.show();
    cout<<"**************************************"<<endl;


    //申请一个对象,调用有参构造
    Stu s2("zhangsan", 18);          //此时调用了有参构造
    s2.show();
    cout<<"**************************************"<<endl;


    //堆区空间申请对象
    Stu *ptr;          //此时不调用构造函数
    ptr = new Stu;      //此时调用构造函数


    return 0;
}

        7> 构造函数初始化对象可以使用初始化列表完成

                使用方式:类名(形参1,形参2,。。。形参n):成员1(形参1),成员2(形参2),。。。成员n(形参n){函数体内容}

                说明:在构造函数的小括号后,由冒号引出初始化列表,括号外时成员变量,括号内是形参

 

#include <iostream>


using namespace std;


class Stu
{
private:
    string name;
    int age;
    double score;


public:
    //无参构造
    Stu()
    {
        cout<<"Stu的无参构造函数"<<endl;
    }
    //自定义有参构造
    Stu(string n, int a)
    {
        this->name = n;         //对成员的赋值操作
        this->age = a;
        cout<<"Stu::有参构造1"<<endl;
    }


    //定义有参构造:使用初始化列表完成对成员的初始化工作
    Stu(string n, int a, double s):name(n),age(a),score(s)
    {
        cout<<"Stu::有参构造2"<<endl;
    }


    void show()
    {
        cout<<"name = "<<name<<endl;
        cout<<"age = "<<age<<endl;
        cout<<"score = "<<score<<endl;
    }
};




int main()
{
    //在栈区申请对象空间
    Stu s1;                       //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
    s1.show();
    cout<<"**************************************"<<endl;


    //申请一个对象,调用有参构造
    Stu s2("zhangsan", 18);          //此时调用了有参构造
    s2.show();
    cout<<"**************************************"<<endl;




    //申请一个对象,调用有参构造
    Stu s3("lisi", 20, 99.8);
    s3.show();
    cout<<"**************************************"<<endl;




    //堆区空间申请对象
    Stu *ptr;          //此时不调用构造函数
    ptr = new Stu;      //此时调用构造函数


    return 0;
}

        8> 必须使用初始化列表的情况

                1、当构造函数的形参名和成员变量名同名时,可以使用初始化列表来解决

                2、当类中有const修饰的成员变量时,对该变量也必须进行初始化,使用初始化列表解决

                3、当类中有引用成员时,对该成员的操作也必须使用初始化列表完成

                4、当类中有其他类的成员子对象时,对该成员的操作也必须使用初始化列表完成,如果没有使用初始化列表调用有参构造,则系统会自动调用成员子对象的无参构造

 

#include <iostream>


using namespace std;


class Toy
{
public:
    Toy() {cout<<"Toy::无参构造"<<endl;}
    Toy(string n):name(n) {cout<<"Toy::有参构造"<<endl;}


private:
    string name;


};






class Stu
{
private:
    string name;
    int age;
    double score;
    const int value;        //类中有const类型的成员
    int &key;             //类中有引用成员
    Toy t;               //类中有其他类的成员子对象


public:
    //无参构造
    Stu():value(1314), key(*(new int(520))), t("hello kity")
    {


        cout<<"Stu的无参构造函数"<<endl;
    }
    //自定义有参构造
    Stu(string n, int a, int &k):value(1314), key(k)
    {
        this->name = n;         //对成员的赋值操作
        this->age = a;
        cout<<"Stu::有参构造1"<<endl;
    }


    //定义有参构造:使用初始化列表完成对成员的初始化工作
    Stu(string name, int age, double score, int &k):name(name),age(age),score(score),value(1314),key(k)
    {
        cout<<"Stu::有参构造2"<<endl;
    }


    void show()
    {
        cout<<"name = "<<name<<endl;
        cout<<"age = "<<age<<endl;
        cout<<"score = "<<score<<endl;
    }
};


int main()
{
    //在栈区申请对象空间
    Stu s1;                       //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
    s1.show();
    cout<<"**************************************"<<endl;

    //堆区空间申请对象
    Stu *ptr;          //此时不调用构造函数
    ptr = new Stu;      //此时调用构造函数

    return 0;
}

3.3 析构函数 

        1> 功能:在对象消亡时,用于给对象回收空间使用的

        2> 定义格式

1、没有返回值
2、函数名:类名前加个波浪线  ~类名
3、权限:一般为public
4、没有参数,所以,一个类中只有一个析构函数,不能进行重载
5、格式:   ~类名();

        3> 调用时机:当对象的生命周期结束后,用于回收内存空间

                栈区:当栈空间释放后,系统会自动调用该类的析构函数

                堆区:当使用delete关键字释放对象空间时,系统自动调用

        4> 如果没有手动定义析构函数,系统会提供一个析构函数,用于回收类对象的空间,如果手动定义了析构函数,那么,系统就不再提供默认的析构函数了。

#include <iostream>


using namespace std;


class Toy
{
public:
    Toy() {cout<<"Toy::无参构造"<<endl;}
    Toy(string n):name(n) {cout<<"Toy::有参构造"<<endl;}


private:
    string name;


};


class Stu
{
private:
    string name;
    int age;
    double score;
    const int value;        //类中有const类型的成员
    int &key;             //类中有引用成员
    Toy t;               //类中有其他类的成员子对象
    int *ptr;         //指针成员


public:
    //无参构造
    Stu():value(1314), key(*(new int(520))), t("hello kity"), ptr(new int(666))
    {


        cout<<"Stu的无参构造函数"<<endl;
    }
    //自定义有参构造
    Stu(string n, int a, int &k):value(1314), key(k)
    {
        this->name = n;         //对成员的赋值操作
        this->age = a;
        cout<<"Stu::有参构造1"<<endl;
    }


    //定义有参构造:使用初始化列表完成对成员的初始化工作
    Stu(string name, int age, double score, int &k):name(name),age(age),score(score),value(1314),key(k)
    {
        cout<<"Stu::有参构造2"<<endl;
    }


    void show()
    {
        cout<<"name = "<<name<<endl;
        cout<<"age = "<<age<<endl;
        cout<<"score = "<<score<<endl;
    }


    //定义析构函数
    ~Stu()
    {
        delete ptr;          //释放指针的空间
        cout<<"STU::析构函数"<<endl;
    }
};




int main()
{
    //在栈区申请对象空间
    Stu s1;                       //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
    s1.show();
    cout<<"**************************************"<<endl;




    //堆区空间申请对象
    Stu *ptr;          //此时不调用构造函数
    ptr = new Stu;      //此时调用构造函数


    //释放ptr的空间
    delete ptr;       //此时会调用析构函数


    return 0;
}

课外作业:

仿照string类,实现myString

解析:

#include <iostream>
#include <string.h>
using namespace std;

class myString
{
private:
    // 指向字符串的指针
    char *str;
    // 字符串的容量
    int size;

public:
    // 默认构造函数:初始化一个空字符串,初始容量为10
    myString():size(10)
    {
        str = new char[size];
    }

    // 构造函数:接受一个const char指针,复制其内容作为新字符串
    myString(const char *s)
    {
        // 获取传入字符串的长度
        size = strlen(s);
        // 分配足够空间存储字符串,包括终止符'\0'
        str = new char[size+1];
        // 复制传入的字符串到新分配的内存中
        strcpy(str,s);
    }
    //析构函数
    ~myString();
    //判空
    bool empty();
    //长度
    int lenth();
    //打印
    char * c_str();
    //拷贝
    myString(const myString &other);
    //指定位置显示
    char &at(int index)
    {
        return str[index];
    }
    //二倍扩容
    void expend();
};

myString::~myString()
{
    for(int i=0;i<size;i++)
    {
        delete [] &str[i];      //循环删除数据
    }
    delete [] str;    //删除表
}

bool myString::empty()
{
    return size==0;       //判空
} 

int myString::lenth()
{
    return size;       //返回字符串长度
}

char *myString::c_str()
{
    return str;        //返回数据
}

myString::myString(const myString &other)
{
    size = other.size;         //将待拷贝的大小覆盖原来的大小
    str = new char[size+1];     //申请空间
    strcpy(str,other.str);        //复制内容到新列表里
}


void myString::expend()
{
    size *=2;
    char *newstr = new char[size];
    strcpy(newstr,str);
    delete [] str;
    str = newstr;
}


int main()
{
    myString q("adsdvbadf");
    myString q1=q;
    cout<<"拷贝:"<<q1.c_str()<<endl;
    cout<<"数据为:"<<q.c_str()<<endl;
    cout <<"长度为:"<< q.lenth() << endl;
    cout <<"第3个元素为:"<< q.at(2)<<endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值