C++远征离港篇
本文及代码收录于个人编程笔记(整理中,欢迎Star):
https://github.com/mtianyan/Programming-Notebook
离港总动员
C++远征计划的学习者肯定是冲着封装,继承,多态来的。
![mark](https://i-blog.csdnimg.cn/blog_migrate/5b7020802f2f7e503c57a66906f6dd77.png)
知识点:
- 指针 VS 引用
#define VS const(更强数据控制力)
- 函数默认值 & 函数重载
- 内存管理(头疼): 堆中的内存管理几乎完全由程序员操心[出来混总是要还的]
- 封装 继承 多态
c++语言引用
引用类型:
- 什么是引用?
![mark](https://i-blog.csdnimg.cn/blog_migrate/e24a47da3e5fc2bdf99dd812e86328ea.png)
引用就是变量的别名
- 能不能只有别名?
只有别名,别名就变成了真实姓名.只有别名也是无法进行命名的。
基本数据类型的引用
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(void)
{
int a = 3;
// 给a起了一个别名b
int &b = a; //引用必须初始化
b = 10; // 给b赋值10,a的值也就由3变为10
cout << a << endl;
system("pause");
return 0;
}
为a起别名b: 对别名做的操作就是对a本身做了操作[叫小萝卜头干什么,罗某某也干了什么]
结构体类型的引用
使用别名对于结构体做操作的例子:
typedef struct
{
int x;
int y;
}Coor;
#include <stdlib.h>
#include <iostream>
using namespace std;
typedef struct
{
int x;
int y;
}Coor;
int main(void)
{
Coor c1;
Coor &c = c1; // 给c1起了别名c
c.x = 10; // 使用别名对真实值做操作
c.y = 20;
cout << c1.x << endl << c1.y << endl;
system("pause");
return 0;
}
指针类型的引用
类型 *&指针引用名 = 指针;
#include <iostream>
using namespace std;
int main(void)
{
int a = 10;
int *p = &a; // 定义指针p
int *&q = p; // 指针p的别名q
*q = 20;
cout << a << endl;
system("pause");
return 0;
}
-
int a = 10;
// 给a分配一个内存逻辑地址,如0x100001。这个地址存放了值10; -
int *p = &a;
//创建指针变量p指向a,给p分配地址0x100002,这个地址存放的值是"0x100001"(a的逻辑地址值);
-int *&q = p;
// (给指针p起别名q)创建变量q,给q分配地址也是0x100002, 因此这个地址存放的值还是a的逻辑地址值; -
*q = 20;
// (对q做操作)访问存放在q变量地址下的值,获得了a的地址值, 再访问一下a的地址值,修改里面存放的内容为20;
引用作为函数参数
C语言中将两个数的值进行交换:
void fun(int *a, int *b)
{
int c =0;
c =*a;
*a =*b;
*b =c;
}
int main()
{
int x =10;
int y =20;
fun(&x,&y);
return 0;
}
c++中引用实现:
void fun(int &a, int &b)
{
int c =0;
c =a;
a =b;
b =c;
}
int main()
{
int x=10,y=20;
fun(x,y)
return 0;
}
a是x的别名。b是y的别名。 里面操作的就是实际的参数了。
C++语言引用代码演示:
基本数据类型引用示例:
2-2-C++Two-ReferenceDemo/main.cpp
#include <iostream>
#include <stdlib.h>
using namespace std;
int main(void)
{
int a = 10;
int &b = a; // 定义一个引用(别名)
// int &b = NULL; 计算机会报错, 初始化 无法从 int 转换为 int &
b = 20;
cout << a << endl;
a = 30;
cout << b << endl;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/6524f2e4dbc17784f78c743f197ed5dc.png)
对于本体和别名的操作具有相同的效果。
结构体引用示例:
2-2-2-C++Two-ReferenceStructDemo/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
typedef struct
{
int x;
int y;
}Coord; //Coord 坐标
int main(void)
{
Coord c;
Coord &c1 = c; // 起别名c1
c1.x = 10;
c1.y = 20;
cout << c.x << endl << c.y << endl;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/58eee2726c9861769424b4c9a2ae68b2.png)
指针引用示例:
2-2-3-C++Two-ReferencePointerDemo/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(void)
{
int a = 3;
int *p = &a;
int *&q = p; // 指针p的别名q
*q = 5;
cout << a << endl;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/f83bd14a16324d596f57105129f429dd.png)
函数参数引用示例:
2-2-4-C++Two-ReferenceFunctionParameter/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
void fun(int &a, int &b);
int main(void)
{
int x = 10;
int y = 20;
cout << x << endl;
cout << y << endl;
fun(x, y);
cout << "交换后:" << endl;
cout << x << endl;
cout << y << endl;
system("pause");
return 0;
}
void fun(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/a211d5f86a8d69a0aba846847016d5e9.png)
看起来传入的是实参x,y 实际上 a是x的引用,b是y的引用
int a;
int &b = a;
int &c = a;
一个本体可以起多个别名
单元巩固
定义一个引用y,y是x的引用,然后打印x和y的值。将y的值更改之后再次打印,x和y的值。
2-2-5-C++Two-ReferenceUnitDemo/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(void)
{
int x = 3;
//定义引用,y是x的引用
int &y = x;
//打印x和y的值
cout << x << endl;
cout << y << endl;
//修改y的值
y = 5;
//再次打印x和y的值
cout << "After Change Y:" << endl;
cout << x << "," << y << endl;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/1173fb35152b09b34c2030ee576c1c36.png)
C++语言-const
const关键字是用来控制变量是否可以变化的。
const与基本数据类型
没有const之前的情况:
int x =3; //变量
![mark](https://i-blog.csdnimg.cn/blog_migrate/f5ffcd28d853ac03dc5d667d9b82ab16.png)
const int x=3; //此时的x为常量
![mark](https://i-blog.csdnimg.cn/blog_migrate/7f650a62da12ee8c0b1df2a8a8096ef7.png)
x变成了一个常量,无法进行更改。再赋值其他数字,编译时就会报错。
const与指针类型
-
const int *p=NULL;
完全等价于int const *p=NULL
![mark](https://i-blog.csdnimg.cn/blog_migrate/9cfb4a7147ce08f05ddaeedf46a0b5a7.png)
int *const p=NULL
与前两种有区别。
- 除了常规上面加一处,也可以在前面后面都加
const int * const p = NULL;
完全等价于 int const * const p = NULL;
![mark](https://i-blog.csdnimg.cn/blog_migrate/bc96837108788f4488077d3b5fd0008a.png)
int x =3;
const int *p = &x;
// p = &y;正确
// *p = 4;错误
const 修饰
*p
; 因此p可以指向其他的地址,但*P
不可以被改变。
变量名 | 存储地址 | 存储内容 |
---|---|---|
x | &x | 3 |
p | &p | &x |
int x =3;
int *const p = &x;
// p = &y;错误
// *p = 4;正确
const写在了
*
的后面。const 修饰p; const修饰的p只能指向一个地址。
变量名 | 存储地址 | 存储内容 |
---|---|---|
x | &x | 3 |
p | &p | &x (不可改变) |
const 修饰p;
const int x =3;
const int *const p = &x;
// p = &y; 错误
// *p = 4; 错误
变量名 | 存储地址 | 存储内容 |
---|---|---|
x | &x | 3 (不可改变) |
p | &p | &x (不可改变) |
const 与引用
int x =3;
const int &y =x;
// x=10; 正确
// y=20; 错误 y作为别名加了const
变量名 | 存储地址 | 存储内容 |
---|---|---|
x | &x | 3 |
const 实例
//错误
const int x =3;
x =5;
// 常量x不能进行赋值了
//错误
int x =3;
const int y =x ;
y = 5;
// y 变成了常量,不能再赋值
//错误
int x =3;
const int *y =&x; // 修饰*y
*y = 5; // *y不可变化
//错误
int x =3,z=4;
int * const y = &x;
y = &z; // 修饰y 不允许重新指向
//错误
const int x =3;
const int &y =x;
y =5;
//错误:指针会存在改变常量的风险。
const int x =3;
int *y = &x; // x不可变,指针可变。
// 使用一个可变的指针,指向一个不可变的变量。风险是可以通过*y的方式改变x的值。
// 编译器会禁止
//正确。x拥有读写,y只可读。
int x =3;
const int *y =&x; //权限小的接收权限大的
const代码示例
3-2-1-constIntChangeDemo/main.cpp
#include "stdafx.h"
#include <iostream>
using namespace std;
int main(void)
{
const int x = 3;
x = 5;
system("pause");
return 0;
}
1> error C3892: “x”: 不能给常量赋值
通过define和const修饰的都可以达成设置常量目的。
- const的优点是,常量有类型,在编译的时候要检查语法错误。
- 而#define定义的没有数据类型,是宏定义在编译时不再检查语法错误
- 推荐用const来定义常量
3-2-2-constPointerChangeDemo/main.cpp
#include <iostream>
using namespace std;
int main(void)
{
int x = 3;
int y = 4;
int const *p = &x; // const int *p = &x等价
// 都是修饰*p的
//*p = 5;
x = 5;
p = &y;
cout << *p << endl;
system("pause");
return 0;
}
此时*p
的值不能进行修改。但是可以修改p指针指向的地址
错误 C3892 “p”: 不能给常量赋值
![mark](https://i-blog.csdnimg.cn/blog_migrate/b054ef926a9a83246fc7167b75c7a4d2.png)
3-2-3-constPointerChangeDemo2/main.cpp
#include <iostream>
using namespace std;
int main(void)
{
int x = 3;
int y = 5;
int *const p = &x; // const修饰p
// p = &y; // p不能给常量赋值
*p = 10;
cout << x << endl;
system("pause");
return 0;
}
此时对于p指向的地址不能修改。但是对于
*p
的值可以进行修改。
![mark](https://i-blog.csdnimg.cn/blog_migrate/3cb5e85244bbc0eb71488fe380349c8d.png)
3-2-4-constPointerMoveDemo/main.cpp
#include <iostream>
using namespace std;
int main(void)
{
int x = 3;
int y = 5;
int const *p = &x;
cout << *p << endl;
p = &y;
//*p = 10;
cout << *p << endl;
system("pause");
return 0;
}
因为此时const修饰的*p
,而p是可以移动到其他地址。
![mark](https://i-blog.csdnimg.cn/blog_migrate/34428a19e6364563e043ee7635563c9b.png)
const修饰一个引用:
3-2-5-constReferenceDemo/main.cpp
#include <iostream>
using namespace std;
int main(void)
{
int x = 3;
int y = 5;
int const &z = x;
// z = 10; // z不能被改变
x = 20;
cout << x << endl;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/3a4fd18326780dfc1aae6fe3b5885610.png)
别名被限制上了不能修改,但是原变量是可以修改的。
函数中的const
因为可以保证传入函数内部的值不会因为误操作而修改原有值
3-2-6-constFunctionDemo/main.cpp
#include <iostream>
using namespace std;
void fun( const int &a, const int &b);
int main(void)
{
int x = 3;
int y = 5;
fun(x, y);
cout << x << "," << y << endl;
system("pause");
return 0;
}
void fun( const int & a, const int & b)
{
// 错误因为传入的值为const。不能进行修改。
a = 10;
b = 20;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/b3cccd383a85e3e98d5b9e094b59e4ad.png)
把const去掉,因为传入的是引用,所以原始值可以被修改。
而当const修饰之后,传入函数内部的值并不会修改原有值
关于const用法,以下错误的是:
A. int const a = 3; int *p = &a; //
B. int a = 3; int const *p = &a;
C. int a = 3; int * const p = &a;
D. const int a = 3; int const &b = a;
B const 修饰
*p
C const 修饰p
D const修饰a的别名b
mtianyan:指针指向const修饰的变量时,应该是const int const *p = &a;
单元巩固
使用const关键字定义整型变量count,并定义指针p引用变量count。利用for循环打印count次Hello C++
3-4-C++-UnitDemo/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(void)
{
// 定义常量count
const int count = 3;
int const *p = &count;
// 打印count次字符串Hello C++
for (int i = 0; i < *p; i++)
{
cout << "Hello C++" << endl;
}
system("pause");
return 0;
}
因为只对*p
进行了const。因此可以让p指向其他地址。
c++函数新特性
函数参数默认值
void fun(int i, int j=5, int k=10); // j,k有默认值
void fun(int i, int j=6, int k); // 错误写法
有默认参数值的参数必须在参数表的最右端
声明写默认值,定义不建议写。 定义时写默认值有些编译器无法通过。
void fun(int i, int j = 5, int k = 10);
void fun(int i, int j, int k)
{
cout << i << j << k;
}
使用时:
int main()
{
fun(20);
fun(20,30);
fun(20,30,40);
}
无实参则用默认值,否则实参覆盖默认值
函数重载
前提: 在相同作用域下
两个条件:
- 用同一个函数名定义的多个函数
- 参数个数 或 参数类型不同
demo代码:
int getMax(int x, int y, int z)
{
//TO DO
}
double getMax(double x ,double y)
{
//TO DO
}
思考:编译器如何识别重载的函数
实际的编译之后,名称+参数形成新的函数。来区分两个所谓的同名函数。
-
int getMax(int x, int y, int z)
->getMax_int_int_int
-
double getMax(double x ,double y)
->getMax_double_double
调用时,则根据实参类型和个数自动识别。
重载的好处:
- 求几个数最大值,比如有时候求三个数有时候求5个数,有时候求整数,有时候求浮点数。不需要想名字,计算机帮我们决定。
内联函数
![内联函数](http://upload-images.jianshu.io/upload_images/1779926-5cb6ebca6707f613.png?imageMogr2/auto-orient/
strip%7CimageView2/2/w/1240)
主调函数调用普通函数有五个步骤:
- 调用fun(),2. 找到fun()的相关函数入口 3. 执行fun() 中的相关代码 4. 返回主调函数
- 主调函数向下运行其他代码直到结束。
内联函数会在编译时将函数体代码和实参代替函数调用语句。
- 省掉了2和4步骤,会节省时间,尤其是循环调用。
内联函数关键字inline
inline int max(int a,int b,int c);
int main()
{
int i =10,j=20,k=30,m;
m = max(i,j,k);
cout<<"max="<<m<<endl;
return 0;
}
使用时和普通函数一样使用。代码展开后相当于代码粘贴进来。
![mark](https://i-blog.csdnimg.cn/blog_migrate/88cf888bd64969e7f94d3c5b934e5403.png)
![mark](https://i-blog.csdnimg.cn/blog_migrate/928bfcfe1b369776f13e86e206b42a3d.png)
思考: 为什么不所有地方都使用内联函数?
- 内联编译是建议性的,由编译器决定
- 逻辑简单(最好不要包含for循环等),调用频繁的函数建议使用内联
- 递归函数无法使用内联方式。
内容总结
- 函数参数默认值: 实参覆盖默认值
- 函数重载: 名称相同,参数可辩 (个数类型)
- 内联函数: inline 效率高 有条件(1.逻辑简单,2.不能是递归)
C++函数特性代码演示
学习函数默认参数,重载,内联函数。
- 函数参数默认值:
4-2-c++-functionDefaultParameter/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
void fun(int i=30, int j = 20, int k = 10);
int main(void)
{
fun();
fun(100);
fun(100, 200);
fun(100, 200, 300);
system("pause");
return 0;
}
void fun(int i, int j, int k)
{
cout << i << "," << j << "," << k << endl;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/cfce482358e2ff18050f0fc8fdae5aae.png)
已传入的实参覆盖默认值,未传入的使用默认值。 默认值从右侧开始赋值,声明时赋默认值,定义时不要默认值。
函数重载
前提条件,函数在同一个作用域下,默认多个函数在同一个命名空间时。
当没有定义命名空间时,函数同名就默认是重载了。
4-2-C++-FunctionOverload/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
void fun(int i = 30, int j = 20, int k = 10);
void fun(double i = 30.0, double j = 40.0);
int main(void)
{
// fun(); //“fun” : 对重载函数的调用不明确
// 有多个 重载函数 "fun" 实例与参数列表匹配
fun(1, 2);
fun(1.1, 2.2);
system("pause");
return 0;
}
void fun(int i, int j, int k)
{
cout << i << "," << j << "," << k << endl;
}
void fun(double i, double j)
{
cout << i << "," << j << endl;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/14fe1185d4f1da56464258ea609c5ad8.png)
fun() 两个函数都可以,因此编译器懵了。
inline函数实现只需要加上inline关键字
inline void fun(int i = 30, int j = 20, int k = 10);
inline void fun(double i = 30.0, double j = 40.0);
inline这种内联,只是一种编译方式,结果上没有什么不同。
C++的重载的两个函数参数数量可以相同也可以不同, 当参数数量相同时,只需要对应参数类型不同即称为重载。
单元巩固: 代码练习
4-4-ReturnMaxDemo/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
/**
*函数功能:返回a和b的最大值
*a和b是两个整数
*/
int getMax(int a, int b)
{
return a > b ? a : b;
}
/**
* 函数功能:返回数组中的最大值
* arr:整型数组
* count:数组长度
* 该函数是对上面函数的重载
*/
int getMax(int arr[], int count)
{
//定义一个变量并获取数组的第一个元素
int a = arr[0];
for (int i = 1; i < count; i++)
{
//比较变量与下一个元素的大小
if (a <arr[i])
{
//如果数组中的元素比maxNum大,则获取数组中的值
a = arr[i];
}
}
return a;
}
int main(void)
{
//定义int数组并初始化
int numArr[3] = { 3, 8, 6 };
//自动调用int getMax(int a, int b)
cout << getMax(6, 4) << endl;
//自动调用返回数组中最大值的函数返回数组中的最大值
cout << getMax(numArr, 3) << endl;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/8f83a440dd551f434e3b55c6941902e7.png)
C++内存管理
什么是内存管理?
思考:内存的本质是什么?
内存的本质是一种资源,由操作系统掌控。
我们能做什么?
我们可以对内存进行申请和归还操作,申请/归还内存资源称为内存管理。
内存的申请与释放
运算符: new
delete
- 内存的申请:
int *p=new int;
- 释放:
delete p;
这是申请和释放某一个内存
申请和释放块内存
int *arr=new int[10]; // 申请块内存
delete []arr; // 释放快内存
内存操作注意事项
回忆: 申请和释放内存的其他方式
- c语言中:
void *malloc(size_t size); // 使用申请内存函数
void free(void *menblock); // 使用释放内存函数
- c++:
new
delete
运算符
配套使用不要混搭
- 申请内存是否一定成功: 不一定会有那么多内存.
int *p=new int [1000];
if(NULL==p)
{
//内存分配失败
}
释放内存注意:
- 在释放内存后,要将指针值赋为空
int *p=new int [1000];
if(NULL==p)
{
//内存分配失败
}
delete []p;
p = NULL;
int *p=new int;
if(NULL==p)
{
//内存分配失败
}
delete p;
p = NULL;
不置为空,它就会指向刚才那块内存。我们如果再次使用delete,就会造成同一块内存回收两次。
计算机会出现异常。
内容总结:
使用new
申请内存,使用delete
释放内存,配套使用。
申请内存需要判断是否失败。释放内存要记得指针置空。
new和delete配套使用
内存管理代码演示
5-2-NewDeleteMemoryManage/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(void)
{
//int *p = new int(20); //申请同时初始化
int *p = new int;
if (NULL == p)
{
system("pause");
return 0;
}
*p = 20;
cout << *p << endl;
delete p;
p = NULL;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/50740fe9399a91629b4eca60b12cf355.png)
申请块内存:
5-2-2-BlockMemoryManage/main.cpp
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(void)
{
int *p = new int[1000];
if (NULL == p)
{
system("pause");
return 0;
}
p[0] = 10;
p[1] = 20;
cout << p[0] << "," << p[1] << endl;
delete []p; // 注意这里的[],否则只会释放第一块。
p = NULL;
system("pause");
return 0;
}
![mark](https://i-blog.csdnimg.cn/blog_migrate/1b1f8d63d27531a8fc6cb01d47401859.png)
单元巩固
在堆中申请100个char类型的内存,拷贝Hello C++字符串到分配的堆中的内存中,打印字符串,最后释放内存。
5-4-StrcpyMemoryMange/main.cpp
#include <string.h>
#include <iostream>
using namespace std;
int main(void)
{
//在堆中申请100个char类型的内存
char *str = new char[100];
if (NULL == str)
{
system("pause");
return 0;
}
//拷贝Hello C++字符串到分配的堆中的内存中
strcpy_s(str,100,"Hello C++");
//打印字符串
cout << str << endl;
//释放内存
delete[]str;
str = NULL;
system("pause");
return 0;
}
没有与参数列表匹配的 重载函数 "strcpy_s",添加char数组的长度。