头图

0.前言

今天,我们进入c++的学习,我在专栏里提到过,这些课程,来自我在大学自学时候的笔记整理而成,可能有不完善之处,在今天的课程笔记里,我们忽略了一个有兴趣的带入点,c++的起源,在此引用维基百科的解释

图片.png

1.从C语言到C++一些基础语法的变化

1.1 内存的申请和释放

在C语言当中,我们学习的堆空间申请和释放:
申请:malloc
释放:free
在C++当中,推荐使用:
申请:new
释放:delete

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main() 
{
    //1. 在C中,申请10个int的空间
    int* p1 = (int*)malloc(sizeof(int) * 10);
    memset(p1, 0, sizeof(int) * 10);

    //2. 在C++中,申请的方式,使用new,并且在申请的同时可以直接
    //初始化
    //注意:这种写法,申请了10个int,前5个初始值是1,2,3,4,5
    //后面5个就是0
    int* p2 = new int[10]{1,2,3,4,5};
    //注意:这种写法,申请了1个int,初始值是10
    int* p3 = new int(10);

    //3. 释放malloc申请的空间
    free(p1);
    //4. 释放new出来的空间
    // 当初申请的时候,申请了1个以上,释放的时候就需要加[]
    delete[]p2;
    // 当初申请的时候,只申请了1个,释放就无需加[]
    delete p3;
  }

他们有什么区别?
1.malloc和free 他们是函数,new和delete 他们是C++的运算符
2.malloc返回的是一个 void*类型的指针,需要我们自己转换的。new申请什么类型就得到什么类型的指针,不需要强制转换的。
3.malloc不会调用类的构造函数,free不会调用类的析构函数。new会调用构造函数,delete会调用析构函数。
4.C++推荐使用new和delete

1.2 函数的重载


//实现一个函数,得到两个整型数据的较大值
int GetMaxInt(int a, int b)
{
    if (a>b)
    {
        return a;
    }
    else
    {
        return b;
    }
}
//又有新需求,得到两个浮点型数据的较大值
double GetMaxDouble(double a, double b)
{
    if (a > b)
    {
        return a;
    }
    else
    {
        return b;
    }
}

int main()
{
    int n = GetMaxInt(10, 20);
    double m = GetMaxDouble(10.5, 7.8);
    return 0;
}

上面这个代码,也是可以的。但是两个函数的功能实际是一致的,都是获取较大值。但是函数名却不一样,那么就会增大我们记忆的负担。需要记住很多的函数名。
C++提供了一个比较好的机制,可以减轻这样的负担------- 函数重载
函数重载:在相同的作用域内,函数的名字相同,但是参数不同,这样可以构成重载,构成重载之后,在调用函数的时候,会根据传递的参数,自动选择调用哪个函数。
参数不同:
a.类型不同
b.顺序不同
c.个数不同

//实现一个函数,得到两个整型数据的较大值
int GetMax(int a, int b)
{
    if (a > b)
    {
        return a;
    }
    else
    {
        return b;
    }
}
//又有新需求,得到两个浮点型数据的较大值
double GetMax(double a, double b)
{
    if (a > b)
    {
        return a;
    }
    else
    {
        return b;
    }
}

int main()
{
    GetMax(20, 10);
    GetMax(20.8, 10.5);
    return 0;
}

只有返回值类型不同,不能构成重载的:
图片.png
使用函数重载的好处:
我们不需要去维护,记忆很多的函数名,使用起来比较便利。
实际上,重载是一种多态机制,接口复用

名称粉碎机制,C++的函数名,也要把参数类型算进去,是重载的底层机制。
图片.png
如果不要名称粉碎的话,可以在函数的前面 加上
extern "C" 这样一个声明,就会以 C的方式编译函数,不会名称粉碎了,也就不能重载了。

1.3 默认参数

//获取自由落体的速度

double GetV(double t, double g = 9.8)
{
    return t * g;
}


int main()
{
    double v = GetV(5);
    GetV(10);
    GetV(20);
    GetV(20, 9.8 / 6);
    return 0;
}

一些需要注意的地方:
1.默认值只能从右往左设置,中间不能间断
图片.png

2.当一个函数既有声明,又有定义的时候,默认参数只能写在声明中。
图片.png

3.当同时出现默认参数和函数重载的时候,容易造成二义性问题

#include <stdio.h>

int GetAdd(int a, int b, int c, int d = 0, int e = 0)
{
    return a + b + c + d + e;
}
int GetAdd(int a, int b, int c)
{
    return a + b + c;
}
int main()
{
    GetAdd(1, 2, 3);
    return 0;
}

图片.png

1.4 引用

#include <stdio.h>
int main()
{
    //1. 定义一个变量
    int nNum = 100;
    //2. 定义一个引用,引用nNum
    //这里& 不是取地址,而是用于定义类型的
    int& a = nNum;//a就是nNum的别名
    a = 200;
    printf("%d %d\n", a, nNum);
    nNum = 500;
    printf("%d %d\n", a, nNum);
    //背后的原理是什么呢??
    //a只是一个名字,没有自己的空间,和被引用的对象nNum公用内存
    printf("%p %p", &a, &nNum);
    return 0;
}

有什么用???
可以代替指针的一部分功能:

#include <stdio.h>
void swap(int& a, int& b)
{
    int n = a;
    a = b;
    b = n;
}
int main()
{
    int nNum1 = 10;
    int nNum2 = 20;
    swap(nNum1, nNum2);
    printf("%d %d", nNum1, nNum2);
    return 0;
}

引用能够做到的事情,指针也是可以的。为什么要使用引用呢???
引用和指针有什么区别???(整体来说,引用比较安全)
1.引用必须初始化,指针可以不初始化。
2.引用一旦初始化,就不能引用其他位置了。
3.指针是一个变量,有自己的内存,引用是一个别名,没有自己的内存

#include <stdio.h>
int main()
{
    //1. 引用必须要初始化,指针不必
    int nNum = 100;
    int& a = nNum;
    int* p = nullptr;
    p = &nNum;
    //2. 引用一经初始化,就不能再引用其他位置了
    int nNum2 = 200;
    a = nNum2;//这个叫复制
    p = &nNum2; //p指向了新的位置
    //3. 指针的本质是一个变量,有名字,有自己的空间(用来存地址的)
    //   引用是没有自己的空间的,是依赖于被引用的对象存在而存在的
    return 0;
}

目前,咱们学习了3种传参方式:
1.数值传递
2.指针传递:本质上,还是数值传递,只是这个数值是个地址。
3.引用传递:形参和实参共享内存,这里就可以说 是形参改变了实参

1.5 C++的输入和输出

在C语言种,我们使用的是printf和scanf_s 实现的输入和输出。在C++中有了新的方式:
输出:cout
输入:cin
配合流运算符: 输出流 << 输入流 >>
不能直接使用
图片.png

1.5.1 命名空间

需要通过命名空间去使用命名空间:是一种防止命名冲突的机制有三种方式:using namespace std; //直接打开命名空间中的所有内容,全部都能直接使用了
图片
using std::cout  ;//只打开了 cout  使用谁打开谁
图片
使用cout的时候,加上命名空间的名字, 使用 ::  作用域符号
图片

一般使用 方式3或者方式2,方式1 不推荐

#include <iostream>
//using namespace std;
//using std::cout;
int main()
{
    std::cout << "helloworld"<<std::endl;
    std::cout << 10 << std::endl;
    std::cout <<3.32453245+8 << std::endl<<10<<20<<300<<'a';
    return 0;
}

图片.png

1.5.2 cin的使用

#include <iostream>
//using namespace std;
using std::cin;

int main()
{
    //1. 输入一个整数
    int nNum = 0;
    cin >> nNum;
    //2. 输入一个字符
    char cCh = 0;
    cin >> cCh;
    //3. 输入一个小数
    double fNum = 0;
    cin >> fNum;
    //4. 输入一个字符串
    char buf[50] = {};
    char* p = new char[100]{ 0 };
    //gets_s(buf); 可以接收空格
    cin >> buf;
    cin >> p;
    return 0;
}

2.类的基本语法

2.1 理解类的语法

定义学生结构体,并且能够进行结构体的一些使用

#include <iostream>
//using namespace std;
using std::cin;

struct STUDENT {
    char  szName[20]; //姓名
    int nId;          //学号
    int nScore;       //分数
};

void PrintfStu(STUDENT stu)
{
    printf("%s ", stu.szName);
    printf("%d ", stu.nId);
    printf("%d ", stu.nScore);
}

void PrintfStu(STUDENT* pstu)
{
    printf("%s ", pstu->szName);
    printf("%d ", pstu->nId);
    printf("%d ", pstu->nScore);
}
void SetStu(STUDENT* pstu,const char* szName,int nId,int nScore)
{
    //pstu->szName = szName
    strcpy_s(pstu->szName, szName);
    pstu->nId = nId;
    pstu->nScore = nScore;
}
int main()
{
    STUDENT stu1 = { "xiaoming",20,90 };
    PrintfStu(&stu1);
    SetStu(&stu1, "xiaohong", 21, 95);
    PrintfStu(&stu1);

    return 0;
}

在函数传参的时候,如果参数需要是结构体的话,一般使用指针,优点有两个:
1.传递的数据量比较小的,只有4个字节
2.能够修改外部的数据

以上的代码,都是学习过的,在C语言程序开发中,也是没有问题的。
但是,这里有一个天然的缺点:
需要由程序员自己去维护函数和变量之间的使用关系,比如:
我们需要很清楚 PrintfStu,SetStu使用的是STUDENT这个结构体。
在程序规模比较小的时候,这个是比较好维护的。
当程序规模很大之后,再去维护他们的关系就是一个比较大的负担了。比如有好几百个结构体,好几千个函数。
此时有一个新的语法,能解决这个问题,就是类。

2.2 类的语法

#include <iostream>
//关键字:class
//类名:一般以C开头,后面是一个单词,首字母大写
//类和结构体一样,都是自定义的数据类型
//这个数据类型中,可以包含变量,也可以包含函数
class CStudent {
public:
    void PrintfStu()
    {
        printf("%s ", this->szName);
        printf("%d ", this->nId);
        printf("%d ", this->nScore);
    }
    void SetStu( const char* szName, int nId, int nScore)
    {
        //pstu->szName = szName
        strcpy_s(this->szName, szName);
        this->nId = nId;
        this->nScore = nScore;
    }
private:
    char  szName[20]; //姓名
    int nId;          //学号
    int nScore;       //分数
};
int main()
{
    //定义的这个变量,即包含了数据,又能直接使用函数
    CStudent stu1 ;
    stu1.SetStu("xiaoming", 20, 90);
    stu1.PrintfStu();
    stu1.SetStu("xiaohong", 21, 95);
    return 0;
}

总结:
1.这么写了之后,数据和操作这个数据的函数,在语法上就有了联系,他们之间的关系就不需要我们维护了。
2.类:class定义出来的新类型 对象:使用类定义的变量
3.类中的函数,称之为 成员函数 或者 成员方法 方法 行为
4.类中的变量,称之为 成员变量 或者 属性 类中数据

2.3 类中的权限:

类中的数据和函数,有一个权限的概念:
访问对象中的数据,通常有两种方式:
1.通过对象直接访问
2.通过对象调用对象自己的函数,由对象自己的函数去访问(自己的函数访问自己的数据)
public(公有的): 可以通过类的对象 直接去使用的成员
protected(保护的):不可以通过类的对象 直接去使用的成员 (在继承的时候再详细讲)
private(私有的): 不可以通过类的对象 直接去使用的成员
权限体现的也是封装性的思想:
通常来说,将数据定义为私有,这样的话,使用类的人就不能随意的修改数据,更为安全。具体怎么使用这些数据,都需要通过类提供的函数。整个程序就会更为安全。

2.4 this指针

当我们定义一个对象的时候,只定义出来了新的数据成员。函数在内存永远都只有一份
图片.png

在SetStu这个函数中,如何区分要修改哪一个对象的数据???就是通过this指针。
this指针本质上来说,就是对象的地址。是默认传递进去的。

#include <iostream>
//关键字:class
//类名:一般以C开头,后面是一个单词,首字母大写
//类和结构体一样,都是自定义的数据类型
//这个数据类型中,可以包含变量,也可以包含函数
class CStudent {
public:
    void PrintfStu()
    {
        printf("%s ", this->szName);
        printf("%d ", this->nId);
        printf("%d ", this->nScore);
    }
    void SetStu( const char* szName, int nId, int nScore)
    {
        //pstu->szName = szName
        strcpy_s(this->szName, szName);
        this->nId = nId;
        this->nScore = nScore;
    }
private:
    char  szName[20]; //姓名
private:
    int nId;          //学号
public:
    int nScore;       //分数
};
int main()
{
    //定义的这个变量,即包含了数据,又能直接使用函数
    CStudent stu1 ;
    CStudent stu2;
    CStudent stu3;
    stu1.SetStu("xiaoming", 20, 90);//stu1.SetStu(&stu1,"xiaoming", 20, 90);
    stu1.PrintfStu();//stu1.PrintfStu(&stu1);
    stu1.SetStu("xiaohong", 21, 95);

    stu1.nScore = 50;
    stu2.SetStu("xiaobai", 15, 88);//stu2.SetStu(&stu2,"xiaobai", 15, 88);
    return 0;
}

2.5 类中函数的编写位置

咱们演示的时候,把函数写在了类中。
但是实际情况,一般把函数都是写在类外的

图片

图片


瞿小凯
1.3k 声望593 粉丝