头图

前言

有同学私信反馈最近两天的内容有点难,因为速度太快,再此我说声抱歉,因为进入c++阶段后,我没有讲c++基础,而是直接进入了c++操作和高级语法部分,因此特别对内容做回顾和补充,今天我特意讲的很细,目的就是要把这部分全方位彻底让你吃透

重载,重写与重定义的区别

图片.png
什么是重载
一个类中,函数名相同,函数的形参的类型或者数目不一样
重写也叫覆盖
子类重新定义父类中有相同名称和参数的虚函数。
重定义也叫做隐藏
子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。
如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。

深拷贝,浅拷贝

为了讲明白这个问题,我特意写了点代码
图片.png
我定义了一个字符串,hello,然后定义一个指针指向这个字符串,根据指针的特性,很明显指向的第一个字符,h。
因此这时候输出结果变成了qello。

内容补充:字符串是字符数组吗?
这部分内容来自百度某回答的,我感觉讲的很不错,所以放过来

    #include <stdio.h>
    #include <string.h>
    int main(void)
    {
        /*字符数组赋初值*/
        char cArr[] = {'I','L','O','V','E','C'};
        /*字符串赋初值*/
        char sArr[] = "ILOVEC";
        /*用sizeof()求长度*/
        printf("cArr的长度=%d\n", sizeof(cArr));
        printf("sArr的长度=%d\n", sizeof(sArr));
        /*用strlen()求长度*/
        printf("cArr的长度=%d\n", strlen(cArr));
        printf("sArr的长度=%d\n", strlen(sArr));
        /*用printf的%s打印内容*/
        printf("cArr的内容=%s\n", cArr);
        printf("sArr的内容=%s\n", sArr);
        return 0;
    }

运行结果为:
cArr的长度=6
sArr的长度=7
cArr的长度=7
sArr的长度=6
cArr的内容=ILOVEC'
sArr的内容=ILOVEC

从代码及其运行结果中可以看出如下几点。

首先,从概念上讲,cArr 是一个字符数组,而 sArr 是一个字符串。因此,对于 sArr,编译时会自动在末尾增加一个 null 字符(也就是'\0',用十六进制表示为 0x00);而对于 cArr,则不会自动增加任何东西。

记住,这里的 sArr 必须是“char sArr[7]="ILOVEC"”,而不能够是“char sArr[6]="ILOVEC"”。

其次,“sizeof()”运算符求的是字符数组的长度,而不是字符串长度。因此,对于“sizeof(cArr)”,其运行结果为 6;而对于 sizeof(sArr),其运行结果为 7(之所以为 7,是因为 sArr 是一个字符串,编译时会自动在末尾增加一个 null 字符)。因此,对于以下代码:

    /*字符数组赋初值*/
    char cArr[] = {'I','L','O','V','E','C'};
    /*字符串赋初值*/
    char sArr[] = "ILOVEC";

好了,补充到这里结束了,我们继续浅拷贝的话题。

我们这时候重新定义下,我们新申请了一段堆空间
图片.png

为了不被我今天的内容绕晕,我们再次解释下什么是堆空间(本截图来自我第八课的博客)
图片.png

另外解释下为什么+1,因为hello这个字符串后面有个\0作为结尾,而streln在计算长度时没有将' \0 '计算在内

另外解释下strlen后面的第二个括号,这一步是为了初始化我们定义的堆空间
图片.png

相当于memset函数。可能你忘了memset了,我们再回顾下。
图片.png

好了,我们观察到,这时候我们运行的hello,值并没有发生变化。并没有给改成pello,这是因为申请了新的堆空间,并不在原来的空间做操作。
图片.png

作用域

作用域是程序的一个区域,一般来说有三个地方可以定义变量:

在函数或一个代码块内部声明的变量,称为局部变量。

在函数参数的定义中声明的变量,称为形式参数。

在所有函数外部声明的变量,称为全局变量。

局部变量

在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。下面的实例使用了局部变量:


#include <iostream>
using namespace std;
 
int main ()
{
  // 局部变量声明
  int a, b;
  int c;
 
  // 实际初始化
  a = 10;
  b = 20;
  c = a + b;
 
  cout << c;
 
  return 0;
}

全局变量

在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。

全局变量可以被任何函数访问。也就是说,全局变量一旦声明,在整个程序中都是可用的。下面的实例使用了全局变量和局部变量:


#include <iostream>
using namespace std;
 
// 全局变量声明
int g;
 
int main ()
{
  // 局部变量声明
  int a, b;
 
  // 实际初始化
  a = 10;
  b = 20;
  g = a + b;
 
  cout << g;
 
  return 0;
}

作用域符号

在C ++中,作用域运算符为::,是运算符中等级最高的,它分为三种:全局作用域符,类作用域符,命名空间作用域符

1.当存在具有相同名称的局部变量时,要访问全局变量:

#include<iostream>  
using namespace std; 
 
int x;  // Global x 
 
int main() 
{ 
  int x = 10; // Local x 
  cout << "Value of global x is " << ::x; 
  cout << "\nValue of local x is " << x;   
  return 0; 
} 

输出结果:
图片.png

2.在类之外定义函数

#include<iostream>  
using namespace std; 
 
class A  
{ 
public:  
 
   // Only declaration 
   void fun(); 
}; 
 
// Definition outside class using :: 
void A::fun() 
{ 
   cout << "fun() called"; 
} 
 
int main() 
{ 
   A a; 
   a.fun(); 
   return 0; 
} 

作用域对命名空间

如果两个命名空间中都存在一个具有相同名称的类,则可以将名称空间名称与作用域解析运算符一起使用,以引用该类而不会发生任何冲突

#include<iostream> 
int main(){ 
    std::cout << "Hello" << std::endl;
} 

作用域类嵌套

如果另一个类中存在一个类,我们可以使用嵌套类使用作用域运算符来引用嵌套的类

#include<iostream> 
using namespace std; 
 
class outside 
{ 
public: 
      int x; 
      class inside 
      { 
      public: 
            int x; 
            static int y;  
            int foo(); 
 
      }; 
}; 
int outside::inside::y = 5;  
 
int main(){ 
    outside A; 
    outside::inside B; 
 
} 

this指针什么时候该用?

在C++里面,每一个对象都能通过this指针来访问自己的地址。

this是所有成员函数的隐藏参数。

在C++中,当成员函数中某个变量与成员变量名字相同,则使用this关键字来表示成员变量。

 或者,需要返回类变量或者结构体变量的时候,使用this关键字。

    #include <iostream>
    using namespace std;
    class Student{
    public:
        void setname(char *name);
        void setage(int age);
        void setscore(float score);
        void show();
    private:
        char *name;
        int age;
        float score;
    };
    void Student::setname(char *name){
        this->name = name;
    }
    void Student::setage(int age){
        this->age = age;
    }
    void Student::setscore(float score){
        this->score = score;
    }
    void Student::show(){
        cout<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;
    }
    int main(){
        Student *pstu = new Student;
        pstu -> setname("李华");
        pstu -> setage(16);
        pstu -> setscore(96.5);
        pstu -> show();
        return 0;
    }

在线编译器运行结果(一般推荐本地编译器,因为我手上有工作,所以用线上的):
图片.png

构造函数,析构函数

在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。这种特殊的成员函数就是构造函数(Constructor)。

    #include <iostream>
    using namespace std;
    class Student{
    private:
        char *m_name;
        int m_age;
        float m_score;
    public:
        //声明构造函数
        Student(char *name, int age, float score);
        //声明普通成员函数
        void show();
    };
    //定义构造函数
    Student::Student(char *name, int age, float score){
        m_name = name;
        m_age = age;
        m_score = score;
    }
    //定义普通成员函数
    void Student::show(){
        cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
    }
    int main(){
        //创建对象时向构造函数传参
        Student stu("小明", 15, 92.5f);
        stu.show();
        //创建对象时向构造函数传参
        Student *pstu = new Student("李华", 16, 96);
        pstu -> show();
        return 0;
    }

图片.png

构造函数的重载

和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。

    #include <iostream>
    using namespace std;
    class Student{
    private:
        char *m_name;
        int m_age;
        float m_score;
    public:
        Student();
        Student(char *name, int age, float score);
        void setname(char *name);
        void setage(int age);
        void setscore(float score);
        void show();
    };
    Student::Student(){
        m_name = NULL;
        m_age = 0;
        m_score = 0.0;
    }
    Student::Student(char *name, int age, float score){
        m_name = name;
        m_age = age;
        m_score = score;
    }
    void Student::setname(char *name){
        m_name = name;
    }
    void Student::setage(int age){
        m_age = age;
    }
    void Student::setscore(float score){
        m_score = score;
    }
    void Student::show(){
        if(m_name == NULL || m_age <= 0){
            cout<<"成员变量还未初始化"<<endl;
        }else{
            cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
        }
    }
    int main(){
        //调用构造函数 Student(char *, int, float)
        Student stu("小明", 15, 92.5f);
        stu.show();
        //调用构造函数 Student()
        Student *pstu = new Student();
        pstu -> show();
        pstu -> setname("李华");
        pstu -> setage(16);
        pstu -> setscore(96);
        pstu -> show();
        return 0;
    }

最后,再来一个文字总结,作为今天的结束:
类的成员有成员变量和成员函数两种。

成员函数之间可以互相调用,成员函数内部可以访问成员变量。

私有成员只能在类的成员函数内部访问。默认情况下,class 类的成员是私有的,struct 类的成员是公有的。

可以用“对象名.成员名”、“引用名.成员名”、“对象指针->成员名”的方法访问对象的成员变量或调用成员函数。成员函数被调用时,可以用上述三种方法指定函数是作用在哪个对象上的。

对象所占用的存储空间的大小等于各成员变量所占用的存储空间的大小之和(如果不考虑成员变量对齐问题的话)。

定义类时,如果一个构造函数都不写,则编译器自动生成默认(无参)构造函数和复制构造函数。如果编写了构造函数,则编译器不自动生成默认构造函数。一个类不一定会有默认构造函数,但一定会有复制构造函数。

任何生成对象的语句都要说明对象是用哪个构造函数初始化的。即便定义对象数组,也要对数组中的每个元素如何初始化进行说明。如果不说明,则编译器认为对象是用默认构造函数或参数全部可以省略的构造函数初始化。在这种情况下,如果类没有默认构造函数或参数全部可以省略的构造函数,则编译出错。

对象在消亡时会调用析构函数。

每个对象有各自的一份普通成员变量,但是静态成员变量只有一份,被所有对象所共享。静态成员函数不具体作用于某个对象。即便对象不存在,也可以访问类的静态成员。静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数。

常量对象上面不能执行非常量成员函数,只能执行常量成员函数。

包含成员对象的类叫封闭类。任何能够生成封闭类对象的语句,都要说明对象中包含的成员对象是如何初始化的。如果不说明,则编译器认为成员对象是用默认构造函数或参数全部可以省略的构造函数初始化。

在封闭类的构造函数的初始化列表中可以说明成员对象如何初始化。封闭类对象生成时,先执行成员对象的构造函数,再执行自身的构造函数;封闭类对象消亡时,先执行自身的析构函数,再执行成员对象的析构函数。

const 成员和引用成员必须在构造函数的初始化列表中初始化,此后值不可修改。

友元分为友元函数和友元类。友元关系不能传递。

成员函数中出现的 this 指针,就是指向成员函数所作用的对象的指针。因此,静态成员函数内部不能出现 this 指针。成员函数实际上的参数个数比表面上看到的多一个,多出来的参数就是 this 指针。


瞿小凯
1.3k 声望593 粉丝