0.前言
今天,我们进入c++的学习,我在专栏里提到过,这些课程,来自我在大学自学时候的笔记整理而成,可能有不完善之处,在今天的课程笔记里,我们忽略了一个有兴趣的带入点,c++的起源,在此引用维基百科的解释
1.从C语言到C++一些基础语法的变化
1.1 内存的申请和释放
在C语言当中,我们学习的堆空间申请和释放:
申请:malloc
释放:free
在C++当中,推荐使用:
申请:new
释放:delete
#include
#include
#include
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;
}
只有返回值类型不同,不能构成重载的:
使用函数重载的好处:
我们不需要去维护,记忆很多的函数名,使用起来比较便利。
实际上,重载是一种多态机制,接口复用
名称粉碎机制,C++的函数名,也要把参数类型算进去,是重载的底层机制。
如果不要名称粉碎的话,可以在函数的前面 加上
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.默认值只能从右往左设置,中间不能间断
2.当一个函数既有声明,又有定义的时候,默认参数只能写在声明中。
3.当同时出现默认参数和函数重载的时候,容易造成二义性问题
#include
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;
}
1.4 引用
#include
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
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
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
配合流运算符: 输出流 << 输入流 >>
不能直接使用
1.5.1 命名空间
需要通过命名空间去使用命名空间:是一种防止命名冲突的机制有三种方式:using namespace std; //直接打开命名空间中的所有内容,全部都能直接使用了
using std::cout ;//只打开了 cout 使用谁打开谁
使用cout的时候,加上命名空间的名字, 使用 :: 作用域符号
一般使用 方式3或者方式2,方式1 不推荐
#include
//using namespace std;
//using std::cout;
int main()
{
std::cout << "helloworld"<
1.5.2 cin的使用
#include
//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
//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
//关键字: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指针
当我们定义一个对象的时候,只定义出来了新的数据成员。函数在内存永远都只有一份
在SetStu这个函数中,如何区分要修改哪一个对象的数据???就是通过this指针。
this指针本质上来说,就是对象的地址。是默认传递进去的。
#include
//关键字: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 类中函数的编写位置
咱们演示的时候,把函数写在了类中。
但是实际情况,一般把函数都是写在类外的