前言:
笔记来源于C++黑马程序员网课视频:https://www.bilibili.com/video/BV1et411b73Z
在此发布笔记,只是为方便学习,不做其他用途,原作者为黑马程序员。
1. C++基础
1.1 用Visual Studio写C++程序输出Hello World
1.1.1 下载并配置Visual Studio环境
1.1.2 用Visual Studio写C++程序
代码:
//在屏幕中输出 Hello World
#include<iostream>
using namespace std;
int main() {
cout << "Hello World" << endl; //屏幕显示“Hello World”
system("pause"); //按任意键继续,退出程序
return 0;
}
/*
Hello World
请按任意键继续. . .
*/
1. main函数
① main函数是一个程序的入口
② 每个程序都必须有这么一个函数,并且有且只有一个。
2. 打印
① cout << "XXXX" << endl; 会使得屏幕打印双引号里面的字符串。
② system("pause"); 会使得屏幕的打印界面暂停到该语句这里,按任意键后才会继续运行。
1.2 注释、变量、常量、标识符、关键字
1.2.1 注释
① 在代码中的加一些说明和解释,这些说明和解释就叫注释。
② 注释能方便自己和其他程序员阅读代码。
③ C++的注释,有两种格式:
1.单行注释: // 描述信息
通常放在一行代码的上方,或者一条语句的末尾,对该行代码说明。
2.多行注释: /* 描述信息 */
编译器在编译代码时,会忽略注释的内容,即不会执行。
1.2.2 变量
① 变量的作用就是就是给一段指定的内存空间起名,方便操作这段内存。
② 语法:数据类型 变量名 = 初始值;
③ 变量存在的意义:方便我们管理内存空间。
④ 每一段内存都有一个地址编号。
⑤ C++规定在创建一个常量或变量时,必须要指出相应的数据类型,否则无法给变量分配内存。
#include <iostream>
using namespace std;
int main()
{
//变量创建的语法:数据类型 变量名 = 变量初始值
int a = 10;
cout << "a = " << a << endl; // 输出语句 cout << "提示语" << 变量 << endl;
system("pause");
return 0;
}
/*
a = 10
请按任意键继续. . .
*/
1.2.3 常量
① 常量,用于记录程序中不可更改的数据。
② C++定义常量两种方式:
1.define 宏常量:#define 常量名 常量值
通常在文件上方定义,表示一个常量。
2.const修饰的变量:const 数据类型 常量名 = 常量值
通常在变量定义前加关键字const,修饰该变量为常量,不可修改。
#define 常量名 常量值
#include <iostream>
using namespace std;
#define Day 7
int main()
{
/*
Day = 14; //错误,Day是常量,一旦修改就会报错
cout << "一周总共有:" << Day << "天" << endl;
*/
cout << "一周总共有:" << Day << "天" << endl;
// 输出语句:cout << "提示语" << 变量 << endl;
system("pause");
return 0;
}
/*
一周总共有:7天
请按任意键继续. . .
*/
const 数据类型 常量名 = 常量值
#include <iostream>
using namespace std;
int main()
{
/*
const int month = 12;
month = 24; //错误,const修饰的变量也称为常量
*/
int month = 12;
month = 24; //正确,这样可以修改变量的值
cout << "一年总共有:" << month << "个月" << endl;
system("pause");
return 0;
}
/*
一年总共有:24个月
请按任意键继续. . .
*/
1.2.4 关键字
① 关键字是C++中预先保留的单词(标识符)。
② 在定义变量或者常量的时候,不要用关键字。
关键字表:
1. int关键字
#include <iostream>
using namespace std;
int main()
{
// int int = 10; // 错误,第二个int是关键字,不可以作为变量的名称
system("pause");
return 0;
}
/*
请按任意键继续. . .
*/
2. sizeof关键字
① sizeof关键字可以统计数据类型所占内存大小。
② sizeof关键字,书写语法:sizeof ( 数据类型 / 变量 )
#include <iostream>
using namespace std;
int main()
{
cout << "short 类型所占内存空间为:" << sizeof(short) << endl;
system("pause");
return 0;
}
/*
short 类型所占内存空间为:2
请按任意键继续. . .
*/
1.2.5 标识符
① 标识符就是 C++ 给变量、常量的命名。
② 标识符有一套自己的命名规则:
- 标识符不能使关键字。
- 标识符只能由字母、数字、下划线组成。
- 第一个字符不能为数字。
- 标识符中字母区分大小写。
③ 建议:给标识符命名时,争取做到见名知意,方便自己和他人的阅读。
#include <iostream>
using namespace std;
int main()
{
//1、标识符不可以是关键字
//int int = 10; //报错
//2、标识符由字母、数字、下划线构成
int abc = 10;
int _abc = 20;
int _123abc = 40;
//3、标识符第一个字符只能是字母或下划线
//int 123abc = 50; //报错
//4、标识符区分大小写
int aaa = 100;
//cout << AAA << endl; //报错,AAA和aaa不是同一个名称
//建议:给变量起名的时候,最好能够做到见名知意
int num1 = 10;
int num2 = 20;
int sum = num1 + num2; //建议用num1、num2、sum表示加法,而不是用a、b、c来表示
cout << sum << endl;
system("pause");
return 0;
}
/*
30
请按任意键继续. . .
*/
2. C++的数据类型
2.1.1 整形
① 作用:整型变量表示的是整数类型的数据。
② C++中能够表示整形的类型有以下几种方式,区别在于所占内存空间不同。
③ 数据类型 占用空间 取值范围
- short(短整型) 2字节 -2^15-2^15-1
- int(整型) 4字节 -2^31-2^31-1
- long(长整型) Windows为4字节,Linux为4字节(32位),8字节(64位) -2^31-2^31-1
- long long(长长整型) 8字节 -2^63-2^63-1
#include <iostream>
using namespace std;
int main()
{
//1、短整型(-32768~32767)
short num1 = 10;
short num5 = 32768; //溢出了,打印出来是-23768
short num6 = 32770; //溢出了,打印出来是-32766
//2、整型
int num2 = 10;
//3、长整型
long num3 = 10;
//4、长长整型
long long num4 = 10;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
cout << "num3 = " << num3 << endl;
cout << "num4 = " << num4 << endl;
cout << "num5 = " << num5 << endl;
cout << "num6 = " << num6 << endl;
system("pause");
return 0;
}
/*
num1 = 10
num2 = 10
num3 = 10
num4 = 10
num5 = -32768
num6 = -32766
请按任意键继续. . .
*/
2.1.2 实型(浮点型)
① 作用:用于表示小数。
② 浮点型变量分为下面两种,两个的区别在于表示有效数字范围不同。
- 单精度float
- 双精度double
③ 数据类型 占用字节 有效数字范围
- float 4字节 7位有效数字
- double 8字节 15-16位有效数字
2.1.2.1 单精度、双精度
#include <iostream>
using namespace std;
int main()
{
//1、单精度 float
//2、双精度 double
//默认情况下,无论单精度还是双精度,都只会显示6位有效数字
float f1 = 3.1415926; //默认为双精度,前面加float相当于把双精度转换为单精度
float f2 = 3.1415926f; //后面加一个f,直接创建的是单精度
cout << "f1 = :" << f1 << endl;
double d1 = 3.1415926;
cout << "d1 = :" << d1 << endl;
cout << "float 占用内存空间为" << sizeof(float) << endl;
cout << "double 占用内层空间为" << sizeof(double) << endl;
system("pause");
return 0;
}
/*
f1 = :3.14159
d1 = :3.14159
float 占用内存空间为4
double 占用内层空间为8
请按任意键继续. . .
*/
2.1.2.2 科学计数法
#include <iostream>
using namespace std;
int main()
{
//科学计数法
float f3 = 3e2; // 3 * (10 ^ 2)
cout << "f3 = :" << f3 << endl;
float f4 = 3e-2; // 3 * (0.1 ^ 2)
cout << "f4 = :" << f4 << endl;
system("pause");
return 0;
}
/*
f3 = :300
f4 = :0.03
请按任意键继续. . .
*/
2.1.3 字符型
① 字符型变量用于显示单个字符。
② 语法:char ch = 'a';
③ 在显示字符型变量时,用单引号字符括起来,不要用双引号。
④ 单引号内只能有一个字符,不可以是字符串。
⑤ C和C++中字符型变量只占用1个字节。
⑥ 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元。
⑦ ASCII码大致由以下两部分组成:
- ASCII非打印控制字符:ASCII表上的数字0-31分配给了控制字符,用于控制像打印机等一些外围设备。
- ASCII打印字符:数字21-126分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。
#include <iostream>
using namespace std;
int main()
{
//1、字符型变量创建方式
char ch = 'a';
cout << ch << endl;
//2、字符型变量所占内存大小
cout << "char字符型变量所占内存:" << sizeof(char) << endl;
//3、字符型变量常见错误
char ch2 = 'b'; //创建字符型变量时候,要用单引号
//char ch3 = "b"; //错误,创建字符型变量时候,不能用双引号
//char ch4 = 'abcdef'; //错误,创建字符型变量时候,单引号内只能有一个字符
//4、字符型变量对应ASCII编码
cout << (int)ch << endl; // (int)ch 为字符'a'的ASCIII码值
system("pause");
return 0;
}
/*
a
char字符型变量所占内存:1
97
请按任意键继续. . .
*/
2.1.4 字符串型
① 用于表示一串字符。
② 字符串型有两种风格
③ C风格字符串:char 变量名[] = "字符串值"
④ C++风格字符串:string 变量名 = "字符串值"
#include <iostream>
using namespace std;
#include <string> //用C++风格字符串时候,要包含这个头文件
int main()
{
//1、C风格字符串
//char 字符 = 这是一个字符
//char 字符[] = 这是一个字符串
//表示字符串时,等号后面,要用双引号
char str[] = "hello world";
cout << str << endl;
//2、C++风格字符串
string str2 = "hello world";
cout << str2 << endl;
system("pause");
return 0;
}
/*
hello world
hello world
请按任意键继续. . .
*/
2.1.5 布尔类型 bool
① 布尔数据类型代表真或假的值。
② bool类型只有两个值:
- true -- 真 (本质是1)
- false -- 假(本质是0)
③ bool类型占1个字节大小
#include <iostream>
using namespace std;
int main()
{
//1、创建bool数据类型
bool flag1 = true; //true代表真
cout << flag1 << endl;
bool flag2 = false; //flag 代表假
cout << flag2 << endl;
//本质上 1代表真 0代表假
//2、查看bool类型所占空间
cout << "bool所占内存空间:" << sizeof(flag1) << endl;
cout << "bool所占内存空间:" << sizeof(flag2) << endl;
system("pause");
return 0;
}
/*
1
0
bool所占内存空间:1
bool所占内存空间:1
请按任意键继续. . .
*/
2.1.6 转义字符
① 用于表示一些不能显示出来的ASCII字符。
② 我们常用的转义字符有:\n、\、\t。
#include <iostream>
using namespace std;
int main()
{
//换行符 \n
cout << "hello world\n";
//反斜杠 \\ 要输出反斜杠\时,要在前面加上一个反斜杠
cout << "\\" << endl;
//水平制表符\t 可以整齐的输出数据
cout << "aaaa\thellowworld" << endl;
cout << "aa\thellowworld" << endl;
cout << "aaaaaa\thellowworld" << endl;
system("pause");
return 0;
}
/*
hello world
\
aaaa hellowworld
aa hellowworld
aaaaaa hellowworld
请按任意键继续. . .
*/
2.1.7 数据输入
① 数据的输入,作用:用于从键盘获取数据。
② 数据的输入,关键字:cin
③ 数据饿输入,语法:cin >> 变量
#include <iostream>
using namespace std;
#include <string> //包含string的头文件
int main()
{
//1、创建bool数据类型
int a = 0;
cout << "请给整型变量 a 赋值:" << endl;
cin >> a;
cout << "整型变量a = " << a << endl;
//2、浮点型
float f = 3.14f;
cout << "请给浮点型变量 f 赋值:" << endl;
cin >> f;
cout << "浮点型变量 f = " << f << endl;
//3、字符型
char ch ; //仅仅声明了,没有初始化
cout << "请给字符型变量 ch 赋值:" << endl;
cin >> ch;
cout << "字符型变量 ch = " << ch << endl;
//4、字符型
string str = "hello";
cout << "请给字符串 str 赋值:" << endl;
cin >> str;
cout << "字符型变量 str = " << str << endl;
//4、布尔型
bool flag = false;
cout << "请给布尔类型 flag 赋值:" << endl;
cin >> flag; //布尔类型 只要是非0的值都代表真
cout << "布尔类型 flag = " << flag << endl;
system("pause");
return 0;
}
/*
请给整型变量 a 赋值:
23
整型变量a = 23
请给浮点型变量 f 赋值:
23.4
浮点型变量 f = 23.4
请给字符型变量 ch 赋值:
w
字符型变量 ch = w
请给字符串 str 赋值:
asfg
字符型变量 str = asfg
请给布尔类型 flag 赋值:
4562
布尔类型 flag = 1
请按任意键继续. . .
*/
3. C++的运算符
① 运算符作用:用于执行代码的运算。
② 运算符类型主要有四种:
- 算术运算符:用于处理四则运算。
- 赋值运算符:用于将表达式的值赋给变量
- 比较运算符:用于表达式的比较,并返回一个真值或假值。
- 逻辑运算法:用于表达式的值返回真值或假值。
3.1 算术运算符
3.1.1 加减乘除运算
#include <iostream>
using namespace std;
int main()
{
int a1 = 10;
int b1 = 3;
cout << a1 + b1 << endl;
cout << a1 - b1 << endl;
cout << a1 * b1 << endl;
cout << a1 / b1 << endl; //两个整数相除,结果依然是整数,将小数部分去除
int a2 = 10;
int b2 = 20;
cout << a2 / b2 << endl;
int a3 = 10;
int b3 = 0;
//cout << a3 / b3 << endl; //两个数相除,除数不可以为0
//两个小数可以相除
double d1 = 0.51; //如果d1为0.5,运算结果为整数2
double d2 = 0.25;
cout << d1 / d2 << endl; //运算结果也可以是小数
system("pause");
return 0;
}
/*
13
7
30
3
0
2.04
请按任意键继续. . .
*/
3.1.2 取模运算
#include <iostream>
using namespace std;
int main()
{
//取摸运算本质 就是求余数
int a1 = 10;
int b1 = 3;
cout << a1 % b1 << endl;
/*两个数相除,除数不可以为0,所以也做不了取模运算
int a2 = 10;
int b2 = 0;
cout << a2 % b2 << endl; //
*/
/*两个小数是不可以做取模运算的
double d1 = 3.14;
double d2 = 1.1;
cout << d1 % d2 << endl;
*/
system("pause");
return 0;
}
/*
1
请按任意键继续. . .
*/
3.1.3 递增递减运算
#include <iostream>
using namespace std;
int main()
{
//1、前置递增
int a = 10;
++a;
cout << "a=" << a << endl;
//2、后置递增
int b = 15;
b++;
cout << "b=" << b << endl;
//3、前置和后置的区别
//前置递增,先让变量+1 然后进行表达式运算
int a2 = 10;
int b2 = ++a2 * 10;
cout << "a2= " << a2 << endl;
cout << "b2= " << b2 << endl;
//后置递增,先进行表达式运算,后让变量+1
int a3 = 10;
int b3 = a3++ * 10;
cout << "a3= " << a3 << endl;
cout << "b3= " << b3 << endl;
system("pause");
return 0;
}
/*
a=11
b=16
a2= 11
b2= 110
a3= 11
b3= 100
请按任意键继续. . .
*/
3.2 赋值运算符
#include <iostream>
using namespace std;
int main()
{
// = 赋值
int a = 10;
a = 100;
cout << "a= " << a << endl;
// += 加等于
int b = 10;
b = 10;
b += 2;
cout << "b= " << b << endl;
// -= 减等于
int c = 10;
c = 10;
c -= 2;
cout << "c= " << c << endl;
// *= 乘等于
int d = 10;
d = 10;
d *= 2;
cout << "d= " << d << endl;
// /= 除等于
int e = 10;
e = 10;
e /= 2;
cout << "e= " << e << endl;
// %= 模等于
int f = 10;
f = 10;
f %= 2;
cout << "f= " << f << endl;
system("pause");
return 0;
}
/*
a= 100
b= 12
c= 8
d= 20
e= 5
f= 0
请按任意键继续. . .
*/
3.3 比较运算符
① 作用:用于表达式的比较,并返回一个真值或假值。
#include <iostream>
using namespace std;
int main()
{
//比较运算符
// ==
int a = 10;
int b = 20;
cout << (a == b) << endl;
// !=
cout << (a != b) << endl;
// >
cout << (a > b) << endl;
// <
cout << (a < b) << endl;
// >=
cout << (a >= b) << endl;
// <=
cout << (a <= b) << endl;
system("pause");
return 0;
}
/*
0
1
0
1
0
1
请按任意键继续. . .
*/
3.4 逻辑运算符
① 作用:用于根据表达式的值返回真值或假值。
- !a 表示非,如果a为假,则!a为真;如果a为真,则!a为假。
- a&&b 表示与,如果a和b都为真,则结果为真,否则为假。
- a||b 表示或,如果a和b有一个为真,则结果为真,二者都为假时,结果为假。
#include <iostream>
using namespace std;
int main()
{
//逻辑运算符
// !=
int a = 10;
int b = 30;
cout << (!a) << endl; //在C++中除了0,都为真
cout << (!!a) << endl;
// &&
a = 10;
b = 30;
cout << (a && b) << endl;
a = 0;
b = 10;
cout << (a && b) << endl;
// ||
a = 10;
b = 30;
cout << (a || b) << endl;
a = 0;
b = 10;
cout << (a || b) << endl;
a = 0;
b = 0;
cout << (a || b) << endl;
system("pause");
return 0;
}
/*
0
1
1
0
1
1
0
请按任意键继续. . .
*/
4. C++的程序流程结构
① C/C++支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构
- 顺序结构:程序按顺序执行,不发生跳转
- 选择结构:依据条件是否满足,有选择的执行相应功能
- 循环结构:依据条件是否满足,循环多次执行某段代码
4.1 选择结构
4.1.1 单行 if 语句
① 单行if语句,格式:if(条件){条件满足执行的语句}
#include <iostream>
using namespace std;
int main()
{
// 选择结构 单行if语句
// 用户输入分数,如果分数大于600,视为考上一本大学,在屏幕上输出
//1、用户输入分数
int score = 0;
cout << "请输入一个分数:" << endl; //在C++中除了0,都为真
cin >> score;
//2、打印用户输入的分数
cout << "您输入的分数为 :" << score << endl;
//3、判断分数是否大于600,如果大于,那么输出
//注意事项,if条件后面不要加分号。加了分号条件不管满足还是不满足,后面的都会运行
if (score > 600)
{
cout << "恭喜您考上了一本大学" << endl;
}
system("pause");
return 0;
}
/*
请输入一个分数:
611
您输入的分数为 :611
恭喜您考上了一本大学
请按任意键继续. . .
*/
4.1.2 单行 else 语句
① 单行else语句,格式:if(条件){条件满足执行的语句} else{条件不满足执行的语句}
#include <iostream>
using namespace std;
int main()
{
// 选择结构 多行if语句
// 输入考试分数,如果分数大于600,视为考上一本大学,在屏幕上输出
// 如果没考上一本大学,打印未考上一本大学
//1、输入考试分数
int score = 0;
cout << "请输入一个考试分数" << endl;
cin >> score;
//2、提示用户输入的分数
cout << "您输入的分数为:" << score << endl;
//3、判断 如果大于600,打印考上一本,否则打印未考上一本大学
if (score > 600) //大于600分,执行下面大括号中内容
{
cout << "恭喜您考上了一本大学" << endl;
}
else //不大于600分,执行else后大括号中内容
{
cout << "未考上一本大学" << endl;
}
system("pause");
return 0;
}
/*
请输入一个考试分数
490
您输入的分数为:490
未考上一本大学
请按任意键继续. . .
*/
4.1.3 多条件的 if 语句
① 多条件的if语句,格式:if(条件1){条件1满足执行的语句}else if(条件){条件2满足执行的语句}....else{都不满足时执行的语句}
#include <iostream>
using namespace std;
int main()
{
// 选择结构 多条件if语句
// 输入一个考试分数,
// 如果分数大于600,视为考上一本大学,在屏幕上输出
// 如果分数大于500,视为考上二本大学,在屏幕上输出
// 如果分数大于400,视为考上三本大学,在屏幕上输出
// 如果小于等于400,视为未考上本科,屏幕上输出
//1、输入考试分数
int score = 0;
cout << "请输入一个考试分数" << endl;
cin >> score;
//2、提示用户输入的分数
cout << "您输入的分数为:" << score << endl;
//3、判断
// 如果大于600,考上一本
// 如果大于500,考上二本
// 如果大于400,考上三本
// 如果小于等于400,未考上本科
if (score > 600)
{
cout << "恭喜您考上了一本大学" << endl;
}
else if (score > 500)
{
cout << "恭喜您考上了二本大学" << endl;
}
else if (score > 400)
{
cout << "恭喜您考上了三本大学" << endl;
}
else
{
cout << "未考上本科大学" << endl;
}
system("pause");
return 0;
}
/*
请输入一个考试分数
430
您输入的分数为:430
恭喜您考上了三本大学
请按任意键继续. . .
*/
4.1.4 嵌套 if 语句
① 嵌套if语句,在if语句中可以嵌套使用if语句,达到更精确的条件判断。
#include <iostream>
using namespace std;
int main()
{
// 选择结构 嵌套if语句
//1、提示高考分数
int score = 0;
cout << "请输入一个考试分数" << endl;
cin >> score;
//2、提示用户输入的分数
cout << "您输入的分数为:" << score << endl;
//3、判断
if (score > 600)
{
cout << "恭喜您考上了一本大学" << endl;
if (score > 700)
{
cout << "您能考上了北京大学" << endl;
}
else if (score > 650)
{
cout << "您能考上了清华大学" << endl;
}
else
{
cout << "您能考上了人名大学" << endl;
}
}
else if (score > 500)
{
cout << "恭喜您考上了二本大学" << endl;
}
else if (score > 400)
{
cout << "恭喜您考上了三本大学" << endl;
}
else
{
cout << "未考上本科大学" << endl;
}
system("pause");
return 0;
}
/*
请输入一个考试分数
670
您输入的分数为:670
恭喜您考上了一本大学
您能考上了清华大学
请按任意键继续. . .
*/
4.1.5 三目运算符
① 作用:通过三目运算符实现简单的判断。
② 语法:表达式1 ? 表达式2 : 表达式3
- 如果表达式1的值为真,执行表达式2,并返回表达式2的结果。
- 如果表达式1的值为假,执行表达式3,并返回表达式3的结果。
③ 三目运算符可以用在显示语句中,cout << "c = " << c == 1 ? "男" : "女" << endl。
#include <iostream>
using namespace std;
int main()
{
// 创建三个变量 a b c
// 将a和b做比较,将变量大的值赋值给变量c
int a = 10;
int b = 20;
int c = 0;
c = (a > b ? a : b); //可以将运算的结果的变量的值赋值给变量
cout << "c = " << c << endl;
//在C++中三目运算符返回的是变量,可以继续赋值
(a > b ? a : b) = 100;
cout << "a = " << a << endl; //可以将运算的结果右值作为变量来赋值
cout << "b = " << b << endl;
system("pause");
return 0;
}
/*
c = 20
a = 10
b = 100
请按任意键继续. . .
*/
4.1.6 switch 语句
① switch语句用于执行多条件分支语句。
② switch语句中表达式类型只能是整型或者字符型。
③ case里如果没有break,那么程序会一直向下执行。
④ 与if语句比,对于多条件判断时,switch的结构清晰,执行效率高,缺点是switch不可以判断区间。
#include <iostream>
using namespace std;
int main()
{
//1、提示用户给电影评分
cout << "请给电影进行评分" << endl;
//2、用户开始进行打分
int score = 0;
cin >> score;
cout << "您打的分数为:" << score << endl;
//3、根据用户输入的分数来提示用户最后的结果
switch (score)
{
case 10:
cout << "您认为是经典的电影" << endl;
break;
case 9:
cout << "您认为是经典的电影" << endl;
break;
case 8:
cout << "您认为电影非常好" << endl;
break;
case 7:
cout << "您认为电影还行" << endl;
break;
case 6:
cout << "您认为电影一般" << endl;
break;
default: //前面都不满足,就会执行default语句
cout << "您认为电影是烂片" << endl;
break;
}
//if 和 switch 区别:
//switch 缺点,判断的时候只能是整型或者字符型,不可以是一个区间。
//switch 优点,结构清晰,执行效率高。
system("pause");
return 0;
}
/*
请给电影进行评分
8
您打的分数为:8
您认为电影非常好
请按任意键继续. . .
*/
4.1.7 三只小猪称体重
题目:现有三只小猪ABC,请分别输入三只小猪的体重,并且判断哪只小猪最重。
#include <iostream>
using namespace std;
int main()
{
//三只小猪称体重,判断哪只最重
//1、创建三只小猪的体重变量
int num1 = 0;
int num2 = 0;
int num3 = 0;
//2、让用户输入三只小猪的重量
cout << "请输入小猪A的体重" << endl;
cin >> num1;
cout << "请输入小猪B的体重" << endl;
cin >> num2;
cout << "请输入小猪C的体重" << endl;
cin >> num3;
cout << "小猪A的体重为:" << num1 << endl;
cout << "小猪B的体重为:" << num2 << endl;
cout << "小猪C的体重为:" << num3 << endl;
//3、判断哪只最重
//先判断A和B重量
if (num1 > num2) //A比B重
{
if (num1 > num3) //A比C重
{
cout << "小猪A最重" << endl;
}
else
{
cout << "小猪C最重" << endl;
}
}
else //B比A重
{
if (num2 > num3) //B比C重
{
cout << "小猪B最重" << endl;
}
else //C比B重
{
cout << "小猪C最重" << endl;
}
}
system("pause");
return 0;
}
/*
请输入小猪A的体重
100
请输入小猪B的体重
230
请输入小猪C的体重
300
小猪A的体重为:100
小猪B的体重为:230
小猪C的体重为:300
小猪C最重
请按任意键继续. . .
*/
4.2 循环结构
4.2.1 while 语句
① 作用:满足循环条件时,执行循环语句。
② 语法:while(循环条件) {循环语句}。
③ 解释:只要循环条件的结果为真,就执行循环语句。
#include <iostream>
using namespace std;
int main()
{
//while循环
//在屏幕中打印 0~9 这10个数字
int num = 0;
//while()中填入循环条件
//在写循环的时候,一定要避免死循环的出现
while (num < 10) //如果循环条件写1,为表示死循环,一直循环下去
{
cout << num << endl;
num++;
}
// 在执行循环语句时候,程序必须提供跳出循环的出口,否则出现死循环。
system("pause");
return 0;
}
/*
0
1
2
3
4
5
6
7
8
9
请按任意键继续. . .
*/
4.2.2 do while 语句
① 满足循环条件,执行循环语句。
② 语法:do{循环语句} while(循环条件);
③ 与while的区别在于 do...while会先执行一次循环语句,再判断循环条件。
#include <iostream>
using namespace std;
int main()
{
//do...while语句
//在屏幕中输出 0 到 9 这10个数字
int num = 0;
do
{
cout << num << endl;
num++;
}
while (num < 10);
//do...while和while循环区别在于 do...while会先执行一次循环语句
system("pause");
return 0;
}
/*
0
1
2
3
4
5
6
7
8
9
请按任意键继续. . .
*/
4.2.3 for 语句
① for循环,满足循环条件,执行循环语句。
② for(起始表达式(①);条件表达式②;末尾表达式③) { 循环语句④ }
- 先执行①,初始化
- 再执行②,进行判断
- 再执行④,进行一次循环
- 再执行③,执行一次末尾表达式
③ for循环中的表达式,要用分号进行分隔。
④ while、for...while、for都是开发中常用的循环语句,for循环结构比较清晰,比较常用。
#include <iostream>
using namespace std;
int main()
{
//for循环
//从数字0 打印到 数字9
for (int i = 0; i < 10; i++)
{
cout << i << endl;
}
/*
第二种方式
int i = 0
for (; i < 10; i++)
{
cout << i << endl;
}
*/
/*
第三种方式
int i = 0
for (; ; i++)
{
if(i >= 10)
{
break;
}
cout << i << endl;
}
*/
/*
第四种方式
int i = 0
for (; ; )
{
if(i >= 10)
{
break;
}
cout << i << endl;
i++;
}
*/
system("pause");
return 0;
}
/*
0
1
2
3
4
5
6
7
8
9
请按任意键继续. . .
*/
4.2.4 for 嵌套
① 在循环体中再嵌套一层循环,可以解决一些实际问题,例如我们想在屏幕中打印十行十列的星图,就需要利用嵌套循环。
#include <iostream>
using namespace std;
int main()
{
//利用嵌套循环实现星图
//外层执行一次,内层执行一周
//外层循环
for (int i = 0; i < 10; i++)
{
//内层循环
for (int j = 0; j < 10; j++)
{
cout << "*";
}
cout << endl;
}
system("pause");
return 0;
}
/*
**********
**********
**********
**********
**********
**********
**********
**********
**********
**********
请按任意键继续. . .
*/
4.2.5 猜数字
① 案例描述:系统随机生成一个1到100之间的数字,玩家进行猜测,如果猜错,提示玩家数字过大或过小,如果猜对恭喜玩家胜利,并且退出游戏。
#include <iostream>
using namespace std;
#include<ctime> //time系统时间头文件
int main()
{
//添加随机数种子,作用利用当前系统时间生成随机数,防止每次随机数都一样
srand((unsigned int)time(NULL));
//1、系统生成随机数
int num = rand() % 100 + 1; //rand()%100 生成 0+1 ~ 99+1 随机数
//cout << num << endl; //不显示生成的随机数是多少
//2、玩家进行猜测
int val = 0; //玩家输入的数据
while (1)
{
cin >> val;
//3、判断玩家的判断
if (val > num)
{
cout << "猜测过大" << endl;
}
else if (val < num)
{
cout << "猜测过小" << endl;
}
else
{
cout << "猜测您猜对了" << endl;
break; //break在循环中,可以利用该关键字来退出当前循环
}
}
system("pause");
return 0;
}
/*
32
猜测过小
60
猜测过大
54
猜测过大
40
猜测过大
38
猜测过大
35
猜测过大
33
猜测您猜对了
请按任意键继续. . .
*/
4.2.6 水仙花数
案例描述:水仙花数是指一个3位数,它的每个位上的数字的3次幂之和等于它本身
例如:1^3 + 5^3 + 3^3 = 153
题目:请利用 do...while语句,求出所有3位数中的水仙花数
#include <iostream>
using namespace std;
int main()
{
//1、先打印所有的三位数字
int num = 100;
do
{
//2、从所有三位数中找到水仙花数
int a = 0; //个位
int b = 0; //十位
int c = 0; //百位
a = num % 10; //获取数字的个位
b = num / 10 % 10; //获取数字的十位
c = num / 100; //获取数字的百位
if (a * a * a + b * b * b + c*c*c== num) //如果是水仙花数,才打印
{
cout << num << endl;
}
num++;
} while (num < 1000);
system("pause");
return 0;
}
/*
153
370
371
407
请按任意键继续. . .
*/
4.2.7 敲桌子(逢7过)
案例分析:从1开始数到数字100,如果数字个位含有7,或者数字十位含有7,或者该数字是7的倍数,我们打印敲桌子,其余数字直接打印输出。
#include <iostream>
using namespace std;
int main()
{
//敲桌子案例
for (int i = 1; i <= 100; i++)
{
//2、从100个数字中找到特殊数字,打印“敲桌子”
//如果是7的倍数、个位有7、或者十位有7,打印敲桌子
if (i % 7 == 0 || i % 10 == 7 || i/10 == 7) //如果是特殊数字,打印敲桌子
{
cout << "敲桌子" << endl;
}
else //如果不是特殊数字,才打印数字
{
cout << i << endl;
}
}
system("pause");
return 0;
}
/*
1
2
3
4
5
6
敲桌子
8
9
10
11
12
13
敲桌子
15
16
敲桌子
18
19
20
敲桌子
22
23
24
25
26
敲桌子
敲桌子
29
30
31
32
33
34
敲桌子
36
敲桌子
38
39
40
41
敲桌子
43
44
45
46
敲桌子
48
敲桌子
50
51
52
53
54
55
敲桌子
敲桌子
58
59
60
61
62
敲桌子
64
65
66
敲桌子
68
69
敲桌子
敲桌子
敲桌子
敲桌子
敲桌子
敲桌子
敲桌子
敲桌子
敲桌子
敲桌子
80
81
82
83
敲桌子
85
86
敲桌子
88
89
90
敲桌子
92
93
94
95
96
敲桌子
敲桌子
99
100
请按任意键继续. . .
*/
4.2.8 乘法口诀
案例描述:利用嵌套循环,实现九九乘法表。
#include <iostream>
using namespace std;
int main()
{
//乘法口诀表
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= i; j++)
{
cout << j << "*" << i << "=" << j * i << " ";
}
cout << endl;
}
system("pause");
return 0;
}
/*
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
请按任意键继续. . .
*/
4.3 跳转语句
4.3.1 break 语句
① 用于跳出选择结构或者循环结构。
② break使用时机:
- 出现在switch条件语句中,作用是终止case并跳出switch。
- 出现在循环语句中,作用是跳出当前的循环语句。
- 出现在嵌套循环中,跳出最近的内层循环语句。
③ break如果使用在switch-case中,并且case前面有一大段代码,就会报错,把前面一大堆代码用括号括起来{},编译器认为是一段代码,就不会报错了。
#include <iostream>
using namespace std;
int main()
{
//break的使用时机
//1、出现在switch语句中
cout << "请选择副本难度" << endl;
cout << "1、普通" << endl;
cout << "2、中等" << endl;
cout << "3、困难" << endl;
int select = 0;
cin >> select; //等待用户输入
switch (select)
{
case 1:
cout << "您选择的是普通难度" << endl;
break; //退出switch语句
case 2:
cout << "您选择的是中等难度" << endl;
break;
case 3:
cout << "您选择的是困难难度" << endl;
break;
}
//2、出现在循环语句中
for (int i=0; i < 10; i++)
{
//如果i等于5,退出循环,不再打印
if (i == 5)
{
break; //退出循环
}
cout << i << endl;
}
//3、出现在嵌套循环语句中
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (j == 5)
{
break; //退出内存循环
}
cout << "*";
}
cout << endl;
}
system("pause");
return 0;
}
/*
请选择副本难度
1、普通
2、中等
3、困难
2
您选择的是中等难度
0
1
2
3
4
∗∗∗∗∗
∗∗∗∗∗
∗∗∗∗∗
∗∗∗∗∗
∗∗∗∗∗
∗∗∗∗∗
∗∗∗∗∗
∗∗∗∗∗
∗∗∗∗∗
请按任意键继续. . .
*/
4.3.2 continue 语句
① 在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。
#include <iostream>
using namespace std;
int main()
{
//continue的使用时机
for (int i = 0; i <= 100; i++)
{
// 如果是奇数就输出,如果是偶数就不输出
if (i % 2 == 0)
{
continue; //continue可以筛选条件,执行到此就不再向下执行了,直接执行下一次循环
}
cout << i << endl;
}
system("pause");
return 0;
}
/*
1
3
5
7
9
11
13
15
17
19
21
23
25
27
29
31
33
35
37
39
41
43
45
47
49
51
53
55
57
59
61
63
65
67
69
71
73
75
77
79
81
83
85
87
89
91
93
95
97
99
请按任意键继续. . .
*/
4.3.3 goto 语句
① 可以无条件跳转语句。
② 语法:goto 标记;
- 如果标记名称存在,执行到goto语句时,会跳转到标记的位置。
③ 在程序中不建议使用goto语句,以免造成程序流程混乱。
#include <iostream>
using namespace std;
int main()
{
//continue的使用时机
cout << "1" << endl;
goto FLAG; //这里是分号
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
FLAG: //这里是冒号
cout << "5" << endl;
cout << "6" << endl;
system("pause");
return 0;
}
/*
1
5
6
请按任意键继续. . .
*/
5. C++的数组
5.1 数组特点
① 所谓数组,就是一个集合,里面存放了相同类型的数据元素。
- 特点1:数组中每个元素都是相同的数据类型。
- 特点2:数组是由连续的内存位置组成的。
5.2 一维数组
5.2.1 定义方式
① 一维数组定义:
- 数组类型 数组名 [ 数组长度 ]
- 数组类型 数组名 [ 数组长度 ] = { 值1,值2,.... }
- 数组类型 数组名 [ ] = { 值1,值2,..... }
② 数组名的命名规范与变量名命名规范一致,不要和变量重名。
③ 数组中下标是从0开始索引。
#include <iostream>
using namespace std;
int main()
{
//1、数组类型 数组名 [ 数组长度 ]
int arr[5];
//数组元素的下标是从0开始索引的
//给数组中的元素进行赋值
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
arr[3] = 40;
arr[4] = 50;
//访问数据元素
cout << arr[0] << endl;
cout << arr[1] << endl;
cout << arr[2] << endl;
cout << arr[3] << endl;
cout << arr[4] << endl;
//2、数据类型 数组名 [ 数组长度 ] = { 值1,值2,... }
//如果在初始化数据的时候,没有完全填写完,会用0来填补剩余数据
int arr2[5] = { 10,20,30 };
/*
方式一:
cout << arr2[0] << endl;
cout << arr2[1] << endl;
cout << arr2[2] << endl;
cout << arr2[3] << endl;
cout << arr2[4] << endl;
*/
// 方式二:利用循环输出数组中的元素
for (int i = 0; i < 5; i++)
{
cout << arr2[i] << endl;
}
//3、数据类型 数组名 [ ] = { 值1,值2,.... }
int arr3[] = { 90,80,70,60,50,40,30,20,10 }; //以后自定义变量,不要 int arr3 = 100,这样避免以后调用arr3到底是调用变量还是数组产生模糊。
for (int i = 0; i < 9; i++)
{
cout << arr3[i] << endl;
}
system("pause");
return 0;
}
/*
10
20
30
40
50
10
20
30
0
0
90
80
70
60
50
40
30
20
10
请按任意键继续. . .
*/
5.2.2 数组名
① 一维数组名的用途:
- 可以统计整个数组在内存中的长度。
- 可以获取数组在内存中的首地址。
#include <iostream>
using namespace std;
int main()
{
//数组名用途
//1、可以通过数组名统计整个数组占用内存大小
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << "整个数组占用内存空间为:" << sizeof(arr) << endl;
cout << "每个元素占用内存空间为:" << sizeof(arr[0]) << endl;
cout << "数组中元素的个数为:" << sizeof(arr)/sizeof(arr[0]) << endl;
//2、可以通过数组名查看数组首地址
cout << "数组首地址为:" << (int)arr << endl;
cout << "数组中第一个元素地址为:" << (int)&arr[0] << endl; //int使得将十六位地址通过int转换为10位地址,&表示取址符号,取一个元素的首地址
cout << "数组中第二个元素地址为:" << (int)&arr[1] << endl; //第二个元素和第一个元素差四个字节
//数组名是常量,值为数组首地址,不可以进行赋值操作
//arr = 100; 不可以这样赋值修改
system("pause");
return 0;
}
/*
整个数组占用内存空间为:40
每个元素占用内存空间为:4
数组中元素的个数为:10
数组首地址为:12582108
数组中第一个元素地址为:12582108
数组中第二个元素地址为:12582112
请按任意键继续. . .
*/
5.2.3 五只小猪称体重
案例描述:在一个数组中记录了五只小猪的体重,如:int arr[5]={300,350,200,400,250},找出并打印最重的小猪体重。
#include <iostream>
using namespace std;
int main()
{
//1、创建5只小猪体重的数组
int arr[5] = { 300,350,200,400,250 };
//2、从数组中找到最大值
int max = 0; //先认定一个最大值为0
for (int i = 0; i < 5; i++)
{
//cout << arr[i] << endl;
if (arr[i] > max)
{
max = arr[i];
}
}
//3、打印最大值
cout << max << endl;
system("pause");
return 0;
}
/*
400
请按任意键继续. . .
*/
5.2.4 数组元素逆置
案例描述:请声明一个5个元素的数组,并且将元素逆置。(如原数组元素为:1,3,2,5,4;逆置后输出的结果为:4,5,2,3,1)。
#include <iostream>
using namespace std;
int main()
{
//实现数组元素逆置
//1、创建数组
int arr[5] = { 1,3,2,5,4 };
cout << "数组逆置前" << endl;
for (int i = 0; i < 5; i++)
{
cout << arr[i] << endl;
}
//2、实现逆置
//2.1 记录起始下标位置
//2.2 记录结束下标位置
//2.3 起始下标与结束下标的元素互换
//2.4 起始位置++ 结束位置--
//2.5 循环执行2.1操作,直到起始位置 >= 结束位置
int start = 0; //起始下标
int end = sizeof(arr) / sizeof(arr[0]) - 1; //结束下标,先计算除法
while (start < end)
{
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
//下标更新
start++;
end--;
}
//3、打印逆置后的数组
cout << "数组逆置后" << endl;
for (int i = 0; i < 5; i++)
{
cout << arr[i] << endl;
}
system("pause");
return 0;
}
/*
数组逆置前
1
3
2
5
4
数组逆置后
4
5
2
3
1
请按任意键继续. . .
*/
5.2.5 冒泡排序
① 冒泡排序作用:最常用的排序算法,对数组内元素进行排序。
- 比较相邻的元素,如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的go牛牛公主,执行完毕后,找到第一个最大值。
- 重复以上的步骤,每次比较次数-1.直到不需要比较。
② 示例:将数组(4,2,8,0,5,7,1,3,9)进行排序。
#include <iostream>
using namespace std;
int main()
{
int arr[9] = { 4,2,8,0,5,7,1,3,9 };
cout << "排序前" << endl;
for (int i = 0; i < 9; i++)
{
cout << arr[i] << " ";
}
cout << endl;
//开始冒泡排序
for (int i = 0; i < 9 - 1; i++)
{
//内层循环对比 次数=元素个数=当前轮数-1
for (int j = 0; j < 9 - 1 - i; j++) //最后一位数,已经是最大数了,不需要排序了
{
//如果第一个数字,比第二个数字大,交换两个数字
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j + 1] = temp;
}
}
}
//排序后结果
cout << "排序后" << endl;
for (int i = 0; i < 9; i++)
{
cout << arr[i] << " ";
}
cout << endl;
system("pause");
return 0;
}
/*
排序前
4 2 8 0 5 7 1 3 9
排序后
0 1 2 3 4 5 7 8 9
请按任意键继续. . .
*/
5.3 二维数组
5.3.1 定义方式
① 二维数组就是在一维数组上,多加一个维度。
② 二维数组定义的四种方式:
- 数据类型 数组名 [ 行数 ][ 列数 ];
- 数据类型 数组名 [ 行数 ][ 列数 ] = { { 数据1,数据2} , { 数据3,数据4 } }
- 数据类型 数组名 [ 行数 ][ 列数 ] = { 数据1,数据2,数据3,数据4 };
- 数据类型 数组名 [ ][ 列数 ] = { 数据1,数据2,数据3,数据4 };
③ 以上4种定义方式,利用第二种更加直观,提高代码的可读性。
④ 定义二维数组的时候,如果初始化了数据,可以省略行数。
#include <iostream>
using namespace std;
int main()
{
//1、数据类型 数组名 [ 行数 ][ 列数 ];
int arr[2][3];
arr[0][0] = 1;
arr[0][1] = 2;
arr[0][2] = 3;
arr[1][0] = 4;
arr[1][1] = 5;
arr[1][2] = 6;
//外层循环打印行数,内层循环打印列数
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
cout << arr[i][j] << endl;
}
}
//2、数据类型 数组名 [ 行数 ][ 列数 ] = { { 数据1,数据2} , { 数据3,数据4 } }
int arr2[2][3] =
{
{1,2,3},
{4,5,6}
};
for (int i = 0; i<2;i++)
{
for (int j = 0; j < 3; j++)
{
cout << arr2[i][j] << " "; //打印一个元素后打印一个空格
}
cout << endl; //换行语句
}
//3、数据类型 数组名 [ 行数 ][ 列数 ] = { 数据1,数据2,数据3,数据4 };
int arr3[2][3] = { 1,2,3,4,5,6 }; //可以省去行数,但是列数不能再省略了
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
cout << arr2[i][j] << " ";
}
cout << endl;
}
//4、数据类型 数组名 [ ][ 列数 ] = { 数据1,数据2,数据3,数据4 };
int arr4[][3] = { 1,2,3,4,5,6 }; //可以省去行数,但是列数不可以省略
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
cout << arr2[i][j] << " ";
}
cout << endl;
}
system("pause");
return 0;
}
/*
1
2
3
4
5
6
1 2 3
4 5 6
1 2 3
4 5 6
1 2 3
4 5 6
请按任意键继续. . .
*/
5.3.2 数组名
① 查看二维数组所占内存空间。
② 获取二维数组首地址。
#include <iostream>
using namespace std;
int main()
{
//二维数组名称用途
//1、可以查看占用内存空间大小
int arr[2][3] =
{
{1,2,3},
{4,5,6}
};
cout << "二维数组占用内存空间为: " << sizeof(arr) << endl;
cout << "二维数组第一行占用内存为:" << sizeof(arr[0]) << endl;
cout << "二维数组第一个元素占用内存为:" << sizeof(arr[0][0]) << endl;
cout << "二维数组行数为:" << sizeof(arr)/sizeof(arr[0]) << endl;
cout << "二维数组列数为:" << sizeof(arr[0])/sizeof(arr[0][0]) << endl;
//2、可以查看二维数组的首地址
cout << "二维数组首地址为:" << (int)arr << endl;
cout << "二维数组第一行首地址为:" << (int)arr << endl;
cout << "二维数组第一行首地址为:" << (int)arr[0] << endl;
cout << "二维数组第二行首地址为:" << (int)arr[1] << endl;
cout << "二维数组第一个元素首地址为:" << (int)&arr[0][0] << endl;
cout << "二维数组第二个元素首地址为:" << (int)&arr[0][1] << endl; //每个元素地址相差4个字节
system("pause");
return 0;
}
/*
二维数组占用内存空间为: 24
二维数组第一行占用内存为:12
二维数组第一个元素占用内存为:4
二维数组行数为:2
二维数组列数为:3
二维数组首地址为:3733652
二维数组第一行首地址为:3733652
二维数组第一行首地址为:3733652
二维数组第二行首地址为:3733664
二维数组第一个元素首地址为:3733652
二维数组第二个元素首地址为:3733656
请按任意键继续. . .
*/
5.3.3 考试成绩
案例描述:有三名同学(张三,李四,王五),在一次考试中的成绩分别如下表,请分别输出三名同学的成绩。
姓名 | 语文 | 数学 | 英语 |
---|---|---|---|
张三 | 100 | 100 | 100 |
李四 | 90 | 50 | 100 |
王五 | 60 | 70 | 80 |
#include <iostream>
using namespace std;
int main()
{
//二维数组案例-考试成绩统计
//1、创建二维数组
int scores[3][3] =
{
{100,100,100},
{90,50,100},
{60,70,80}
};
//2、统计每个人的总和分数
for (int i = 0; i < 3; i++)
{
int sum = 0; //统计分数总和不变
for (int j = 0; j < 3; j++)
{
sum += scores[i][j];
//cout << scores[i][j] << " ";
}
cout << "第" << i + 1 << "个人的总分为:" << sum << endl;
}
system("pause");
return 0;
}
/*
第1个人的总分为:300
第2个人的总分为:240
第3个人的总分为:210
请按任意键继续. . .
*/
6. C++的函数
1.1 函数概述
① 函数作用:将一段经常使用的代码封装起来,减少重复代码。
② 一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能。
1.2 函数定义
① 函数的定义主要有5个部分:
- 返回值类型:一个函数可以返回一个值。
- 函数名:给函数起个名称。
- 参数列表:使用该函数时,传入的数据。
- 函数体语句:花括号内的代码,函数内需要执行的语句。
- return表达式:和返回值类型挂钩,函数执行完后,返回相应的数据。
② 语法格式如下所示:
返回值类型 函数名 (参数列表) { 函数体语句 return 表达式 }
#include <iostream>
using namespace std;
//函数的定义
//语法:返回值类型 函数名 (参数列表) { 函数体语句 return表达式 }
//加法函数,实现两个整型相加,并且将相加的结果进行返回
int add(int num1, int num2)
{
int sum = num1 + num2;
return sum;
}
int main()
{
system("pause"); //按任意键继续
return 0;
}
1.3 函数调用
① 功能:使用定义好的函数
② 语法:函数名(参数)
③ 函数定义里小括号内称为形参,函数调用时传入的参数称为实参
#include <iostream>
using namespace std;
//函数的定义
//语法:
//返回值类型 函数名 (参数列表) { 函数体语句 return表达式 }
//定义加法函数
//函数定义的时候,num1和num2并不是真实数据
//它们只是一个形式上的参数,简称形参
int add(int num1, int num2)
{
int sum = num1 + num2;
return sum;
}
int main()
{
//main函数中调用add函数
int a = 10;
int b = 20;
//函数调用语法:函数名称:(参数)
//a和b称为 实际参数,简称实参
//当调用函数的时候,实参的值会传递给形参
int c = add(a, b);
cout << "c = " << c << endl;
system("pause"); //按任意键继续
return 0;
}
/*
c = 30
请按任意键继续. . .
*/
1.4 函数值传递
① 所谓值传递,就是函数调用时实参将数值传入给形参。
② 值传递时,如果形参发生改变,并不影响实参。
③ 在下面代码例子中,实参传进去时,新参会产生新的内存空间赋值,对num1、num2的操作并不会改变实参a、b的值。
#include <iostream>
using namespace std;
//值传递
//定义函数,实现两个数字进行交换函数
//如果函数不需要返回值,声明的时候可以写void
void swap(int num1, int num2)
{
cout << "交换前:" << endl;
cout << "num1= " << num1 << endl;
cout << "num2= " << num2 << endl;
int temp = num1;
num1 = num2;
num2 = temp;
cout << "交换后:" << endl;
cout << "num1= " << num1 << endl;
cout << "num2= " << num2 << endl;
return; //前面写了void,所以不需要返回值。返回值不需要的时候,也可以不写return。
}
int main()
{
//main函数中调用add函数
int a = 10;
int b = 20;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
//当我们把值传递的时候,函数的形参发生发生改变,并不会影响实参
swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause"); //按任意键继续
return 0;
}
/*
a = 10
b = 20
交换前:
num1= 10
num2= 20
交换后:
num1= 20
num2= 10
a = 10
b = 20
*/
1.5 函数常见样式
① 常见的函数样式有四种
- 无参无返
- 有参无返
- 无参有返
- 有参有返
#include <iostream>
using namespace std;
//函数常见样式
//1、无参无返
void test01()
{
cout << "this is test01" << endl;
}
//2、有参无返
void test02(int a)
{
cout << "this is test 02 a = " << a << endl;
}
//3、无参有返
int test03()
{
cout << "this is test 03 " << endl;
return 1000;
}
//4、有参有返
int test04(int a )
{
cout << "this is test 04 a = " << a << endl;
return a;
}
int main()
{
//无参无返函数调用
test01();
//有参无返函数调用
test02(100);
//无参有返函数调用
int num1 = test03();
cout << "num1 = " << num1 << endl;
//有参有返函数调用
int num2 = test04(10000);
cout << "num2 = " << num2 << endl;
system("pause"); //按任意键继续
return 0;
}
/*
this is test01
this is test 02 a = 100
this is test 03
num1 = 1000
this is test 04 a = 10000
num2 = 10000
*/
1.6 函数声明
① 作用:告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
② 函数的声明可以多次,但是函数的定义只能有一次。
#include <iostream>
using namespace std;
//函数的声明
//比较函数,实现两个整型数字进行比较,返回较大的值。
//提前告诉编译器函数的存在,可以利用函数的声明
//函数的声明
//声明可以写多次,但是定义只能有一次
int max(int a, int b);
int max(int a, int b);
int max(int a, int b);
int main()
{
int a = 10;
int b = 20;
cout << max(a, b) << endl;
system("pause"); //按任意键继续
return 0;
}
//函数定义在main函数之后,必须要在main函数之前写函数的声明
int max(int a, int b)
{
return a > b ? a : b;
}
/*
20
按任意键继续...
*/
1.7 函数分文件编写
① 作用:让代码结构更加清晰。
② 函数分文件编写一般有4个步骤:
- 创建后缀名为.h的头文件。
- 创建后缀名为.cpp的源文件。
- 在头文件中写函数的声明。
- 在源文件中写函数的定义。
1.7.1 swap.h头文件
创建swap.h头文件
代码:
//这个是swap.h头文件
#include <iostream>
using namespace std;
//函数的声明
void swap(int a, int b);
1.7.2 swap.cpp源文件
代码:
//这个是swap.cpp源文件
#include "swap.h"
//函数的定义
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
1.7.3 主文件 .cpp文件
代码:
//主文件,调用函数分文件
#include <iostream>
using namespace std;
#include "swap.h" //包含要调用的函数的头文件,双引号表示我们自己写的头文件
int main()
{
int a = 10;
int b = 20;
cout << max(a, b) << endl;
system("pause"); //按任意键继续
return 0;
}
7. C++的指针
7.1 指针概念
① 指针的作用:可以通过指针间接访问内存。
- 内存编号是从0开始记录的,一般用十六进制数字表示。
- 可以利用指针变量保存地址。
7.2 指针变量定义和使用
① 指针变量定义语法:数据类型 * 变量名;
#include <iostream>
using namespace std;
int main()
{
//1、定义指针
int a = 10;
//指针定义的语法:数据类型 * 指针变量
int * p; //创建p为指针变量
//让指针记录变量a的地址
p = &a; // &为取址符号
cout << "a的地址为:" << &a << endl;
cout << "指针p为:" << p << endl; // 指针p存储的值 和变量a的地址是一样的
//2、使用指针
//可以通过解引用的方式来找到指针指向的内存
//指针前加 * 代表解引用,找到指针指向的内存中的数据
*p = 1000; //通过*p访问内存,并修改内存的值
cout << "a= " << a << endl;
cout << "*p:" << *p << endl;
system("pause"); //按任意键继续
return 0;
}
/*
a的地址为:00FBFC78
指针p为:00FBFC78
a= 1000
*p:1000
请按任意键继续. . .
*/
7.3 指针所占内存空间
① 在32位操作系统下,不管什么类型的指针都占4个字节的内存。
② 在64位操作系统下,不管什么类型的指针都占8个字节的内存,但是实际开发环境一般都是32位操作系统下。
#include <iostream>
using namespace std;
int main()
{
//指针所占内存空间
int a = 10;
/*
int * p; //p变量是 int * 数据类
p = &a;
等价于:
int * p = &a;
*/
int * p = &a;
cout << "sizeof(int * )= " << sizeof(p) << endl;
cout << "sizeof(int * )= " << sizeof(int(*)) << endl;
cout << "sizeof(float * )= " << sizeof(float(*)) << endl;
cout << "sizeof(double * )= " << sizeof(double(*)) << endl;
cout << "sizeof(char * )= " << sizeof(char(*)) << endl;
system("pause"); //按任意键继续
return 0;
}
/*
sizeof(int * )= 4
sizeof(int * )= 4
sizeof(float * )= 4
sizeof(double * )= 4
sizeof(char * )= 4
请按任意键继续. . .
*/
7.4 空指针
① 指针变量指向内存中编号为0的空间。
② 用途:初始化指针变量。
③ 空指针指向的内存是不可以访问的。
#include <iostream>
using namespace std;
int main()
{
//空指针
//1、空指针用于给指针变量进行初始化
int* p = NULL;
//2、空指针是不可以进行访问的
//0~255之间的内存编号是系统占用的,因此不可以访问。
*p = 100; //对空指针解引用,然后操作它,这样是报错的
system("pause"); //按任意键继续
return 0;
}
7.5 野指针
① 野指针:指针变量指向非法的内存空间。
② 野指针和空指针都不是我们申请的空间,所以不要访问。
③ 例如,创建了一个整型变量a,是申请了一个整型变量的空间,由于是申请的空间,所以可以通过指针访问它。
#include <iostream>
using namespace std;
int main()
{
//野指针
int* p = (int*)0x1100; // 0x1100是一个十六进制数,int*使得十六进制数强行转换为地址。
// 拿指针随便指向并没有申请这个内存的访问权限的内存。
cout << *p << endl; // 报错,地址并没有申请,你还要去访问它,就会报错
system("pause");
return 0;
}
7.6 const修饰指针
① 看const右侧紧跟着的是指针还是常量,是指针就是常量指针,是常量就是指针常量。
② 如果 const后面跟的是指针(*),就不能做 *p=20 操作,即不能修改指针指向的值。
③ 如果const 后面跟的常量(p),就不能做 p = &b 操作,即不能修改指针的指向。
const在 * 前,修饰整个指针指向的地址中所存放的值,值不可以修改
const在 * 后,修饰特定的指针,指向不可以修改
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 10;
//1、const修饰指针 常量指针
const int* p = &a; // const(常量) *(指针) → 常量指针
// *p = 20; 错误,常量指针 → 指针指向的值不可以改,指针的指向可以改
p = &b; // 正确
//2、const修饰指针 指针常量
int* const p2 = &a; // *(指针)const(常量) → 指针常量
*p2 = 100; //正确的
p2 = &b; //错误,指针的指向不可以改,指针指向的 值可以改
//3、const修饰指针和常量
const int* const p3 = &a;
*p3 = 100; //错误
p3 = &b; //错误
system("pause");
return 0;
}
7.7 指针和数组
① 利用指针访问数组中元素。
#include <iostream>
using namespace std;
int main()
{
//指针和数组
//利用指针访问数组中的元素
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << "第一个元素为:" << arr[0] << endl;
int* p = arr; //数组名arr就是数组的首地址
cout << "利用指针访问第一个元素:" << *p << endl;
p++;
cout << "利用指针访问第二个元素:" << *p << endl;
cout << "利用指针遍历数组:" << endl;
int* p2 = arr;
for (int i = 0; i < 10; i++)
{
//cout << arr[i] << endl;
cout << *p2 << endl;
p2++;
}
system("pause");
return 0;
}
/*
第一个元素为:1
利用指针访问第一个元素:1
利用指针访问第二个元素:2
利用指针遍历数组:
1
2
3
4
5
6
7
8
9
10
请按任意键继续. . .
*/
7.8 指针和函数
① 利用指针作函数的参数,可以修改实参的值。
② 地址传递可以改变实参的数据,值传递不可以改变实参的值。
③ 如果不想修改实参,就用值传递,如果想修改实参,就用地址传递。
④ 代码例子中,temp的值会传递给 *p2 地址中的值,所以a、b实参的值改变了。
#include <iostream>
using namespace std;
void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main()
{
//指针和函数
int a = 10;
int b = 20;
swap(&a, &b);
//如果是地址传递,可以修改实参,原来 a = 10;b = 20,地址传递后 a = 20,b = 10.
cout << "a =" << a << endl;
cout << "b =" << b << endl;
//如果是值传递,不可以修改实参,原来 a = 10;b = 20,值传递后 a = 10,b = 20.
system("pause");
return 0;
}
/*
a =20
b =10
请按任意键继续. . .
*/
7.9 指针配合数组和函数案例
案例描述:封装一个函数,利用冒泡排序,实现对整型数字的升序排列。
例如: int arr[10] = {4,3,6,9,1,2,10,8,7,5}
#include <iostream>
using namespace std;
//冒泡排序函数
void bubbleSort(int * arr, int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - i - 1; j++)
{
//如果j>j+1的值,交换数字
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
//打印数组
void printArray(int* arr, int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << endl;
}
}
int main()
{
//1、先创建数组
int arr[10] = { 4,3,6,1,2,9,10,8,7,5 };
//数组长度
int len = sizeof(arr) / sizeof(arr[0]);
//2、创建函数,实现冒泡排序
bubbleSort(arr, len);
//3、打印排序后的数组
printArray(arr, len);
system("pause");
return 0;
}
/*
1
2
3
4
5
6
7
8
9
10
请按任意键继续. . .
*/
8. C++的结构体
8.1 结构体定义和使用
① 结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
② 语法:struct 结构体名 { 结构体成员列表 }
③ 通过结构体创建变量的方式有三种:
- struct 结构体名 变量名
- struct 结构体名 变量名 = { 成员1值,成员2值 }
- 定义结构体时顺便创建变量
④ 结构体变量利用点.访问成员
#include <iostream>
using namespace std;
#include <string>
//自定义数据类型,一些类型的集合组成一个类型
//语法 struct 类型名称 { 成员列表 }
//结构体定义的时候,struct关键字不能省略
//1、创建学生数据类型:学生包括(姓名,年龄,分数)
struct Student
{
//成员列表
//姓名
string name;
//年龄
int age;
//分数
int score;
}s3; //2.3 创建结构体的时候,顺便创建个结构体变量
int main()
{
//2.1 struct Student s1 s1类似结构体的实例值,类似变量赋值:int a = 10 → 属性 变量 变量值
//结构体创建的时候,struct 关键字可以省略;上面结构体定义的时候 struct 可以省略
struct Student s1;
//给s1属性赋值,通过点.访问结构体变量中的属性
s1.name = "张三";
s1.age = 18;
s1.score = 100;
cout << "姓名:" << s1.name << "年龄:" << s1.age << "分数:" << s1.score << endl;
//2.2 struct Student s2 = { ... }
struct Student s2 = { "李四",19,80 };
cout << "姓名:" << s2.name << "年龄:" << s2.age << "分数:" << s2.score << endl;
//2.3
s3.name = "王五";
s3.age = 20;
s3.score = 60;
cout << "姓名:" << s3.name << "年龄:" << s3.age << "分数:" << s3.score << endl;
system("pause");
return 0;
}
/*
姓名:张三年龄:18分数:100
姓名:李四年龄:19分数:80
姓名:王五年龄:20分数:60
请按任意键继续. . .
*/
8.2 结构体数组
① 作用:将自定义的结构体放入到数组中方便维护。
② 语法:struct 结构体名 数组名[元素个数] = { { , , ... , },{ , , ... , }, ... ,{ , , ... , } }
#include <iostream>
using namespace std;
#include <string>
//自定义数据类型,一些类型的集合组成一个类型
struct Student
{
//成员列表
//姓名
string name;
//年龄
int age;
//分数
int score;
};
int main()
{
//2、创建结构体数组
struct Student stuArray[3] =
{
{"张三",18,100},
{"李四",28,99},
{"王五",38,66}
};
//3、给结构体数组中的元素赋值
stuArray[2].name = "赵六";
stuArray[2].age = 80;
stuArray[2].score = 60;
//4、遍历结构体数组
for (int i = 0; i < 3; i++)
{
cout << "姓名:" << stuArray[i].name << "年龄:" << stuArray[i].age << "分数:" << stuArray[i].score << endl;
}
system("pause");
return 0;
}
/*
姓名:张三年龄:18分数:100
姓名:李四年龄:28分数:99
姓名:赵六年龄:80分数:60
请按任意键继续. . .
*/
8.3 结构体指针
① 作用:通过指针访问结构体中的成员。
② 利用操作符 · > 可以通过结构体指针访问结构体属性。
#include <iostream>
using namespace std;
#include <string>
//结构体指针
//定义学生结构体
struct Student
{
string name; //姓名
int age; //年龄
int score; //分数
};
int main()
{
// 1、创建学生结构体变量,这里的 struct 可以省略
struct Student s = { "张三",18,100 };
//2、通过指针指向结构体变量
struct Student* p = &s; //对s取地址, tudent * p 类似 int * p,这里的 struct 可以省略
//3、通过指针访问结构体变量中的数据
//通过结构体指针 访问结构体中的属性,需要利用'->'
cout << "姓名:" << p->name << "年龄:" << p->age << "分数:" << p->score << endl;
system("pause");
return 0;
}
/*
姓名:张三年龄:18分数:100
请按任意键继续. . .
*/
8.4 结构体嵌套结构体
① 结构体中的成员可以是另一个结构体。
#include <iostream>
using namespace std;
#include <string>
//因为老师的结构体里有学生的结构体,所以学生结构体要在老师结构体前面先定义
//定义学生结构体
struct student
{
string name; //姓名
int age; //年龄
int score; //分数
};
//定义老师结构体
struct teacher
{
int id; //教师编号
string name; //教师姓名
int age; //年龄
struct student stu; //学生结构体
};
int main()
{
//结构体嵌套结构体
//创建老师
teacher t;
t.id = 10000;
t.name = "老王";
t.age = 50;
t.stu.name = "小王";
t.stu.age = 20;
t.stu.score = 60;
cout << "老师姓名:" << t.name << " 老师编号:" << t.id << " 老师年龄:" << t.age
<< " 老师辅导的学生姓名:" << t.stu.name << " 学生年龄:" << t.stu.age
<< " 学生考试分数:" << t.stu.score << endl;
system("pause");
return 0;
}
/*
老师姓名:老王 老师编号:10000 老师年龄:50 老师辅导的学生姓名:小王 学生年龄:20 学生考试分数:60
请按任意键继续. . .
*/
8.5 结构体做函数参数
① 作用:将结构体作为参数向函数中传递,传递方式有两种。
② 传递方式有两种:
- 值传递
- 地址传递
#include <iostream>
using namespace std;
#include <string>
//定义学生结构体
struct student
{
string name; //姓名
int age; //年龄
int score; //分数
};
//定义老师结构体
struct teacher
{
int id; //教师编号
string name; //教师姓名
int age; //年龄
struct student stu; //学生结构体
};
//打印学生信息的函数
//1、值传递
void printStudent1(struct student s)
{
cout << "子函数 值传递前 姓名:" << s.name << "年龄:" << s.age << "分数:" << s.score << endl;
s.age = 100;
}
//2、地址传递
void printStudent2(struct student* p)
{
cout << "子函数 地址传递前 姓名:" << p->name << "年龄:" << p->age << "分数:" << p->score << endl;
p->score = 90;
}
int main()
{
//结构体做函数参数
//将学生传入到一个参数中,打印学生身上的所有信息
//创建结构体变量
struct student s;
s.name = "张三";
s.age = 20;
s.score = 85;
cout << "main函数 传递前 姓名:" << s.name << "年龄:" << s.age << "分数:" << s.score << endl;
printStudent1(s);
cout << "子函数 值传递后 姓名:" << s.name << "年龄:" << s.age << "分数:" << s.score << endl;
printStudent2(&s);
cout << "子函数 地址传递后 姓名:" << s.name << "年龄:" << s.age << "分数:" << s.score << endl;
cout << "main函数 传递后 姓名:" << s.name << "年龄:" << s.age << "分数:" << s.score << endl;
system("pause");
return 0;
}
/*
main函数 传递前 姓名:张三年龄:20分数:85
子函数 值传递前 姓名:张三年龄:20分数:85
子函数 值传递后 姓名:张三年龄:20分数:85
子函数 地址传递前 姓名:张三年龄:20分数:85
子函数 地址传递后 姓名:张三年龄:20分数:90
main函数 传递后 姓名:张三年龄:20分数:90
请按任意键继续. . .
*/
8.6 结构体中const使用
① 作用:const来防止误操作。
#include <iostream>
using namespace std;
#include <string>
//const 的使用场景
struct student
{
//姓名
string name;
//年龄
int age;
//分数
int score;
};
//将函数中形参设置为指针,用地址传递,而不是值传递,可以减少内存空间,而且不会复制新的副本
//值传递需要复制新的副本,如果有成千上万个学生调用结构体,会复制成千上个副本
void printStudents(const student* s)
{
s->age = 150; //报错,因为假如const之后,一旦有修改的操作就会报错,可以防止我们的误操作。
cout << "姓名:" << s->name << "年龄:" << s->age << "分数:" << s->score << endl;
}
int main()
{
//创建结构体变量
struct student s = { "张三",15,70 };
//通过函数打印结构体变量信息
printStudents(&s);
cout << "main中张三年龄为:" << s.age << endl;
system("pause");
return 0;
}
8.7 结构体案例(一)
案例描述:学校正在做毕设项目,每名老师带领5个学生,总共有3名老师,需求如下:设计学生和老师的结构体,其中在老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员,学生的成员有姓名、考试分数、创建数组存放3名老师,通过函数给每个老师及所带的学生赋值。最终打印出老师数据以及老师所带的学生数据。
#include <iostream>
using namespace std;
#include <string>
#include <Ctime>
//学生结构体定义
struct Student
{
//姓名
string sName;
//学生数组
int score;
};
//老师结构体
struct Teacher
{
//姓名
string tName;
//学生数组
struct Student sArray[5];
};
//给老师和学生赋值的函数
void allocateSpace(struct Teacher tArray[], int len) //接收数组的两种方式:一用指针,二用数组名 struct Teacher tArray[]
{
string nameSeed = "ABCDE";
//给老师开始赋值
for (int i = 0; i < len; i++)
{
tArray[i].tName = "Teacher_";
tArray[i].tName += nameSeed[i];
//通过循环给每名老师所带的学生赋值
for (int j = 0; j < 5; j++)
{
tArray[i].sArray[j].sName = "Student_";
tArray[i].sArray[j].sName += nameSeed[j];
int random = rand() % 61 + 40; // 40 ~ 100
tArray[i].sArray[j].score = random; //
}
}
}
//打印所有信息
void printInfo(struct Teacher tArray[], int len)
{
for (int i = 0;i<len;i++)
{
cout << "老师姓名:" << tArray[i].tName << endl;
for (int j = 0; j < 5; j++)
{
cout << "\t学生姓名:" << tArray[i].sArray[j].sName
<< " 考试分数:" << tArray[i].sArray[j].score << endl;
}
}
}
int main()
{
//随机数种子
srand((unsigned int)time(NULL));
//1、创建3名老师的数组
struct Teacher tArray[3];
//2、通过函数给3名老师的信息赋值,并给老师带的学生信息赋值
int len = sizeof(tArray) / sizeof(tArray[0]);
allocateSpace(tArray, len);
//3、打印所有老师及所带的学生信息
printInfo(tArray, len);
system("pause");
return 0;
}
/*
老师姓名:Teacher_A
学生姓名:Student_A 考试分数:57
学生姓名:Student_B 考试分数:84
学生姓名:Student_C 考试分数:85
学生姓名:Student_D 考试分数:43
学生姓名:Student_E 考试分数:62
老师姓名:Teacher_B
学生姓名:Student_A 考试分数:88
学生姓名:Student_B 考试分数:44
学生姓名:Student_C 考试分数:68
学生姓名:Student_D 考试分数:100
学生姓名:Student_E 考试分数:67
老师姓名:Teacher_C
学生姓名:Student_A 考试分数:88
学生姓名:Student_B 考试分数:73
学生姓名:Student_C 考试分数:71
学生姓名:Student_D 考试分数:48
学生姓名:Student_E 考试分数:55
请按任意键继续. . .
*/
8.8 结构体案例(二)
案例描述:设计一个英雄的结构体,包括成员姓名,年龄,性别;创建结构体数组,数组中存放5名英雄。通过冒泡排序算法,将数组中英雄按照年龄进行升序排序,最终打印排序后的结果。
五名英雄信息如下:
- {"刘备",23,"男"}
- {"关羽",22,"男"}
- {"张飞",20,"男"}
- {"赵云",21,"女"}
- {"貂蝉",19,"女"}
#include <iostream>
using namespace std;
//1、设计英雄结构体
struct Hero
{
//姓名
string name;
//年龄
int age;
//性别
string sex;
};
//冒泡排序,实现年龄升序排列
void bubbleSort(struct Hero heroArray[], int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - i - 1; j++)
{
//如果j下标的年龄大于j+1下标的元素的年龄,交换两个元素
if (heroArray[j].age > heroArray[j + 1].age)
{
struct Hero temp = heroArray[j]; //创建一个临时结构体变量
heroArray[j] = heroArray[j + 1];
heroArray[j + 1] = temp;
}
}
}
}
void printHero(struct Hero heroArray[], int len)
{
for (int i = 0; i < len; i++)
{
cout << "姓名:" << heroArray[i].name << " 年龄:" << heroArray[i].age
<< " 性别:" << heroArray[i].sex << endl;
}
}
int main()
{
//2、创建数组存放5名英雄
struct Hero heroArray[5] =
{
{"刘备",23,"男"},
{"关羽",22,"男"},
{"张飞",20,"男"},
{"赵云",21,"女"},
{"貂蝉",19,"女"},
};
int len = sizeof(heroArray) / sizeof(heroArray[0]);
cout << "排序前的打印结果" << endl;
for (int i = 0; i < len; i++)
{
cout << "姓名:" << heroArray[i].name << " 年龄:" << heroArray[i].age
<< " 性别:" << heroArray[i].sex << endl;
}
//3、对数组进行排序,按照年龄进行升序排序
bubbleSort(heroArray, len);
//4、将排序后结果打印输出
cout << "排序后的打印结果" << endl;
printHero(heroArray,len);
system("pause");
return 0;
}
/*
排序前的打印结果
姓名:刘备 年龄:23 性别:男
姓名:关羽 年龄:22 性别:男
姓名:张飞 年龄:20 性别:男
姓名:赵云 年龄:21 性别:女
姓名:貂蝉 年龄:19 性别:女
排序后的打印结果
姓名:貂蝉 年龄:19 性别:女
姓名:张飞 年龄:20 性别:男
姓名:赵云 年龄:21 性别:女
姓名:关羽 年龄:22 性别:男
姓名:刘备 年龄:23 性别:男
请按任意键继续. . .
*/
9. 通讯录管理系统
#include<iostream>
using namespace std;
#include<string>//string头文件
#define MAX 1000
//联系人结构体
struct Person
{
//姓名
string m_Name;
//性别 1 男 2 女
int m_Sex;
//年龄
int m_Age;
//电话
string m_Phone;
//住址
string m_Addr;
};
//通讯录结构体
struct Addressbooks
{
//通讯录中保存的联系人数组
struct Person personArray[MAX];
//通讯录中当前记录联系人个数
int m_Size;
};
//1、添加联系人
void addPerson(Addressbooks* abs)
{
//判断通讯录是否已满,如果满了就不再添加
if (abs->m_Size == MAX)
{
cout << "通讯录已满,无法添加!" << endl;
return;
}
else
{
//添加联系人
//姓名
string name;
cout << "请输入姓名:"<<endl;
cin >> name;
abs->personArray[abs->m_Size].m_Name = name;
//性别
cout << "请输入性别:" << endl;
cout << "1 --- 男" << endl;
cout << "2 --- 女" << endl;
int sex = 0;
while (true)
{
//输入正确,退出循环
//输入错误,重新输入
cin >> sex;
if (sex == 1 || sex == 2)
{
abs->personArray[abs->m_Size].m_Sex = sex;
break;
}
cout << "输入有误,请重新输入" << endl;
}
//年龄
cout << "请输入年龄:" << endl;
int age = 0;
cin >> age;
abs->personArray[abs->m_Size].m_Age = age;
//电话
cout << "请输入电话:" << endl;
string phone;
cin >> phone;
abs->personArray[abs->m_Size].m_Phone = phone;
//住址
cout << "请输入住址:" << endl;
string address;
cin >> address;
abs->personArray[abs->m_Size].m_Addr = address;
//更新通讯录人数
abs->m_Size++;
cout << "添加成功" << endl;
system("pause");//按任意键继续
system("cls");//清屏
}
}
//2、显示所有联系人
void showPerson(Addressbooks* abs)
{
//
//
if (abs->m_Size == 0)
{
cout << "当前记录为空" << endl;
}
else
{
for (int i = 0;i < abs->m_Size;i++)
{
cout << "姓名:" << abs->personArray[i].m_Name << "\t";
cout << "性别:" <<(abs->personArray[i].m_Sex == 1 ?"男":"女") << "\t";
cout << "年龄:" << abs->personArray[i].m_Age << "\t";
cout << "电话:" << abs->personArray[i].m_Phone << "\t";
cout << "住址:" << abs->personArray[i].m_Addr << endl;
}
}
system("pause");//按任意键继续
system("cls");//清屏
}
//3、删除指定联系人
//检测联系人是否存在,如果存在,返回联系人所在数组中的具体位置,不存在返回-1
//参数1 通讯录 参数2 对比姓名
int isExist(Addressbooks* abs, string name)
{
for (int i = 0;i < abs->m_Size;i++)
{
//找到用户输入的名字
if (abs->personArray[i].m_Name == name)
{
return i; //找到了,返回这个人在数组中的下标编号
}
}
return -1;//没找到,返回-1
}
void deletePerson(Addressbooks* abs)
{
cout << "请输入您要删除的联系人" << endl;
string name;
cin >> name;
//ret == -1 未查到,ret!= -1 查到了
int ret = isExist(abs, name);
if (ret != -1)
{
//查到此人,要进行删除操作
for (int i = ret;i < abs->m_Size;i++)
{
//数据前移
abs->personArray[i] = abs->personArray[i + 1];
}
abs->m_Size--;//更新通讯录中的人员数
cout << "删除成功" << endl;
}
else
{
cout << "查无此人" << endl;
}
system("pause");
system("cls");
}
//4、查找指定联系人
void findPerson(Addressbooks* abs)
{
cout << "请输入您要查找的联系人" << endl;
string name;
cin >> name;
//判断指定联系人是否存在通讯录中
int ret = isExist(abs, name);
if (ret != -1)//找到联系人
{
cout << "姓名:" << abs->personArray[ret].m_Name << "\t";
cout << "性别:" << abs->personArray[ret].m_Sex << "\t";
cout << "年龄:" << abs->personArray[ret].m_Age << "\t";
cout << "电话:" << abs->personArray[ret].m_Phone << "\t";
cout << "住址:" << abs->personArray[ret].m_Addr << endl;
}
else//未找到联系人
{
cout << "查无此人" << endl;
}
system("pause");
system("cls");
}
//5、修改指定联系人信息
void modifyPerson(Addressbooks* abs)
{
cout << "请输入您要修改的联系人" << endl;
string name;
cin >> name;
int ret = isExist(abs, name);
if (ret != -1)
{
//姓名
string name;
cout << "请输入名字:" << endl;
cin >> name;
abs->personArray[ret].m_Name = name;
//性别
cout << "请输入性别:" << endl;
cout << "1 --- 男" << endl;
cout << "2 --- 女" << endl;
int sex = 0;
while (true)
{
cin >> sex;
if (sex == 1 || sex == 2)
{
//输入正确,退出循环输入
abs->personArray[ret].m_Sex = sex;
break;
}
cout << "输入有误,请重新输入" << endl;
}
//年龄
cout << "请输入年龄" << endl;
int age = 0;
cin >> age;
abs->personArray[ret].m_Age = age;
//电话
cout << "请输入电话" << endl;
string phone;
cin >> phone;
abs->personArray[ret].m_Phone = phone;
//住址
cout << "请输入住址" << endl;
string address;
cin >> address;
abs->personArray[ret].m_Addr = address;
cout << "修改成功" << endl;
}
else
{
cout << "查无此人" << endl;
}
system("pause");
system("cls");
}
//6、清空所以联系人
void cleanPerson(Addressbooks* abs)
{
abs->m_Size = 0;
cout << "通讯录已清空" << endl;
system("pause");
system("cls");
}
//菜单页面
void showMenu()
{
cout << "*************************" << endl;
cout << "***** 1、添加联系人 *****" << endl;
cout << "***** 2、显示联系人 *****" << endl;
cout << "***** 3、删除联系人 *****" << endl;
cout << "***** 4、查找联系人 *****" << endl;
cout << "***** 5、修改联系人 *****" << endl;
cout << "***** 6、清空联系人 *****" << endl;
cout << "***** 0、退出联系人 *****" << endl;
cout << "*************************" << endl;
}
int main()
{
//创建通讯录结构体变量
Addressbooks abs;
//初始化通讯录中当前人员个数
abs.m_Size = 0;
int select = 0;
while (true)
{
//菜单调用
showMenu();
cin >> select;
switch (select)
{
case 1:
addPerson(&abs);//利用地址传递,可以修饰实参
break;
case 2:
showPerson(&abs);
break;
case 3:
deletePerson(&abs);
break;
case 4:
findPerson(&abs);
break;
case 5:
modifyPerson(&abs);
break;
case 6:
cleanPerson(&abs);
break;
case 0:
cout << "欢迎下次使用" << endl;
system("pause");
return 0;
break;
}
}
system("pause");
return 0;
}
10. 内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程
10.1 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放 CPU 执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此.
全局区还包含了常量区, 字符串常量和其他常量也存放在此.
该区域的数据在程序结束后由操作系统释放.
示例:
#include <iostream>
using namespace std;
//全局变量
int g_a = 10;
int g_b = 10;
//全局常量
const int c_g_a = 10;
const int c_g_b = 10;
int main() {
//局部变量
int a = 10;
int b = 10;
//打印地址
cout << "局部变量a地址为: " << (int)&a << endl;
cout << "局部变量b地址为: " << (int)&b << endl;
cout << "全局变量g_a地址为: " << (int)&g_a << endl;
cout << "全局变量g_b地址为: " << (int)&g_b << endl;
//静态变量
static int s_a = 10;
static int s_b = 10;
cout << "静态变量s_a地址为: " << (int)&s_a << endl;
cout << "静态变量s_b地址为: " << (int)&s_b << endl;
cout << "字符串常量地址为: " << (int)&"hello world" << endl;
cout << "字符串常量地址为: " << (int)&"hello world1" << endl;
cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl;
const int c_l_a = 10;
const int c_l_b = 10;
cout << "局部常量c_l_a地址为: " << (int)&c_l_a << endl;
cout << "局部常量c_l_b地址为: " << (int)&c_l_b << endl;
system("pause");
return 0;
}
/*
局部变量a地址为: 6945460
局部变量b地址为: 6945448
全局变量g_a地址为: 7323648
全局变量g_b地址为: 7323652
静态变量s_a地址为: 7323656
静态变量s_b地址为: 7323660
字符串常量地址为: 7314420
字符串常量地址为: 7314436
全局常量c_g_a地址为: 7314224
全局常量c_g_b地址为: 7314228
局部常量c_l_a地址为: 6945436
局部常量c_l_b地址为: 6945424
请按任意键继续. . .
*/
总结:
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放 const修饰的全局常量 和 字符串常量
10.2 程序运行后
栈区:
由编译器自动分配释放, 存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
代码:
#include <iostream>
using namespace std;
int* func()
{
int a = 10;
return &a;
}
int main() {
int* p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
/*
10
1478068336
请按任意键继续. . .
*/
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
代码:
#include <iostream>
using namespace std;
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
总结:
堆区数据由程序员管理开辟和释放
堆区数据利用new关键字进行开辟内存
10.3 new操作符
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
示例1: 基本语法
#include <iostream>
using namespace std;
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
//利用delete释放堆区数据
delete p;
//cout << *p << endl; //报错,释放的空间不可访问
system("pause");
return 0;
}
示例2:开辟数组
#include <iostream>
using namespace std;
//堆区开辟数组
int main() {
int* arr = new int[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
//释放数组 delete 后加 []
delete[] arr;
system("pause");
return 0;
}
11. 引用
11.1 引用的基本使用
作用: 给变量起别名
语法: 数据类型 &别名 = 原名
示例:
#include <iostream>
using namespace std;
int main() {
int a = 10;
int &b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
11.2 引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
示例:
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int &c = a; //一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
system("pause");
return 0;
}
11.3 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
示例:
#include <iostream>
using namespace std;
//1. 值传递
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址传递
void mySwap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用传递
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap01(a, b);
cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
system("pause");
return 0;
}
总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
11.4 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
示例:
#include <iostream>
using namespace std;
//返回局部变量引用
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& test02() {
static int a = 20;
return a;
}
int main() {
//不能返回局部变量的引用
int& ref = test01();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
//如果函数做左值,那么必须返回引用
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
11.5 引用的本质
本质:引用的本质在c++内部实现是一个指针常量.
讲解示例:
#include <iostream>
using namespace std;
//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
int a = 10;
//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
return 0;
}
结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
11.6 常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
示例:
#include <iostream>
using namespace std;
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
int main() {
//int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
const int& ref = 10;
//ref = 100; //加入const后不可以修改变量
cout << ref << endl;
//函数中利用常量引用防止误操作修改实参
int a = 10;
showValue(a);
system("pause");
return 0;
}
12. 函数提高
12.1 函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。
语法:返回值类型 函数名 (参数= 默认值){}
示例:
#include<iostream>
using namespace std;
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
int func1(int a, int b = 10, int c = 10) {
return a + b + c;
}
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
//3. 如果函数实现有默认值,函数声明的时候就不能有默认参数
int func3(int a, int b);
int func3(int a = 10, int b = 10) {
return a + b;
}
int main() {
//cout << "ret = " << func1() << endl;错误
cout << "ret = " << func1(20) << endl;
cout << "ret = " << func1(20, 20) << endl;
cout << "ret = " << func1(20,20,20) << endl;
cout << "ret = " << func2() << endl;
cout << "ret = " << func3() << endl;
system("pause");
return 0;
}
/*
ret = 40
ret = 50
ret = 60
ret = 20
ret = 20
请按任意键继续. . .
*/
12.2 函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法: 返回值类型 函数名 (数据类型){}
在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术
示例:
#include<iostream>
using namespace std;
void func1(int a) {
cout << "this is func1" << endl;
}
//函数占位参数
void func2(int a, int) {
cout << "this is func2" << endl;
}
//占位参数也可以有默认参数
void func3(int a, int = 10) {
cout << "this is func3" << endl;
}
int main() {
func1(10);
func2(10, 10); //占位参数必须填补
//func2();错误
//func2(10);错误
func3(10);
func3(10,20);
system("pause");
return 0;
}
/*
this is func1
this is func2
this is func3
this is func3
请按任意键继续. . .
*/
12.3 函数重载
12.3.1 函数重载概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件
示例:
#include<iostream>
using namespace std;
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "func 的调用!" << endl;
}
void func(int a)
{
cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
cout << "func (double a)的调用!" << endl;
}
void func(int a, double b)
{
cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a, int b)
{
cout << "func (double a ,int b)的调用!" << endl;
}
/*
函数返回值不可以作为函数重载条件
int func(double a, int b)
{
cout << "func (double a ,int b)的调用!" << endl;
}
*/
int main() {
func();
func(10);
func(3.14);
func(10, 3.14);
func(3.14, 10);
system("pause");
return 0;
}
/*
func 的调用!
func (int a) 的调用!
func (double a)的调用!
func (int a ,double b) 的调用!
func (double a ,int b)的调用!
请按任意键继续. . .
*/
12.3.2 函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
示例:
#include<iostream>
using namespace std;
//函数重载注意事项
//1、引用作为重载条件
void func(int& a)
{
cout << "func (int &a) 调用 " << endl;
}
void func(const int& a)
{
cout << "func (const int &a) 调用 " << endl;
}
//2、函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
cout << "func2(int a, int b = 10) 调用" << endl;
}
void func2(int a)
{
cout << "func2(int a) 调用" << endl;
}
int main() {
int a = 10;
func(a); //调用无const
func(10);//调用有const
//func2(10); //碰到默认参数产生歧义,需要避免
system("pause");
return 0;
}
/*
func (int &a) 调用
func (const int &a) 调用
请按任意键继续. . .
*/
13. 类与对象
面向对象三大特性
① C++面向对象的三大特性为:封装、继承、多态。
② C++认为万事万物皆为对象,对象上有其属性和行为。
③ 例如:
- 人可以作为对象,属性有姓名、年龄、身高、体重......行为有走、跑、跳、吃饭、唱歌......
- 车也可以作为对象,属性有轮胎、方向盘、车灯......行为有载人、放音乐、放空调......
- 具有相同新值的对象,可以抽象为类,人属于人类,车属于车类。
13.1 封装
13.1.1 封装属性和行为
① 封装是C++面向对象三大特性之一。
② 封装的意义一:
- 将属性和行为作为一个整体,表现生活中的事物。
- 将属性和行为加以权限控制。
#include <iostream>
using namespace std;
const double PI = 3.14;
//设计一个圆类,求圆的周长
//圆求周长的公式: 2 * PI * 半径
//class 代表设计一个类,类后面紧跟着的就是类名称
class Circle
{
//访问权限
//公共权限
public: //是冒号,不是分号
//属性
//半径
int m_r;
//行为
//获取圆的周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main()
{
//通过圆类 创建具体的圆(对象)
//实例化 (通过一个类 创建一个对象的过程)
Circle c1;
//给圆对象 的属性进行赋值
c1.m_r = 10;
// 2 * PI * 10 = 62.8
cout << "圆的周长为:" << c1.calculateZC() << endl;
system("pause");
return 0;
}
/*
圆的周长为:62.8
*/
13.1.2 设计学生类
案例描述:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号。
#include <iostream>
using namespace std;
#include<string>
//设计学生类
class Student
{
public: //公共权限
//类中的属性和行为 我们统一称为成员
// 属性 成员属性 成员变量
// 行为 成员函数 成员方法
//属性
string m_Name; //姓名
int m_Id; //学号
//行为
//显示姓名和学号
void showStudent()
{
cout << "姓名:" << m_Name << " 学号:" << m_Id << endl;
}
//给姓名赋值的行为
void setName(string name)
{
m_Name = name;
}
};
int main()
{
//创建一个具体学生 实例化对象
Student s1;
//给s1对象 进行属性赋值操作
s1.m_Name = "张三";
s1.m_Id = 1;
s1.showStudent();
Student s2;
s2.m_Name = "李四";
s2.m_Id = 2;
s2.showStudent();
Student s3;
s3.setName("王五");
s3.m_Id = 3;
s3.showStudent();
system("pause");
return 0;
}
/*
姓名:张三 学号:1
姓名:李四 学号:2
姓名:王五 学号:3
请按任意键继续. . .
*/
13.1.3 封装权限
① 类在设计时,可以把属性和行为放在不同的权限下,加以控制。
② 封装的意义二:
- public 公共权限
- protected 保护权限
- private 私有权限
#include <iostream>
using namespace std;
//访问权限
//三种
//公共权限 public 类内可以访问成员 类外可以访问成员
//保护权限 protected 类内可以访问成员 类外不可以访问成员 子类可以访问父类中的保护内容
//私有权限 private 类内可以访问权限 类外不可以访问成员 子类不可以访问父类中的私有内容
class Person
{
//公共权限
public:
string m_Name; //姓名
protected:
string m_Car; //汽车
private:
int m_Password; //银行卡密码
public:
void func()
{
m_Name = "李四";
m_Car = "奔驰"; //保护权限内容,在类外访问不到
m_Password = 123;
cout << m_Name << endl;
cout << m_Car << endl;
cout << m_Password << endl;
}
};
int main()
{
//实例化具体对象
Person p1;
p1.m_Name = "李四";
cout << p1.m_Name << endl;
//p1.m_Password = 321; 不可访问
//p1.m_Car = "宝马"; 不可访问
p1.func(); //公共权限内容,在类外可以访问
system("pause");
return 0;
}
/*
李四
李四
奔驰
123
请按任意键继续. . .
*/
13.1.4 struct 和 class 区别
① 在C++中struct和class唯一的区别就在于默认的访问权限不同。
② 区别:
- struct 默认权限为公共。
- class 默认权限为私有。
#include <iostream>
using namespace std;
//访问权限
//三种
//公共权限 public 类内可以访问成员 类外可以访问成员
//保护权限 protected 类内可以访问成员 类外不可以访问成员 子类可以访问父类中的保护内容
//私有权限 private 类内可以访问权限 类外不可以访问成员 子类不可以访问父类中的私有内容
class C1
{
int m_A; // 默认权限是私有
};
struct C2
{
int m_A; //默认权限 是公共
};
int main()
{
//struct 和 class 区别
//struct 默认权限是 公共 public
//class 默认权限是 私有 private
C1 c1;
//c1.m_A = 100; //私有权限,不能访问
//cout << c1.m_A << endl;
C2 c2;
c2.m_A = 100; //公共权限,可以访问
cout << c2.m_A << endl;
system("pause");
return 0;
}
/*
100
请按任意键继续. . .
*/
13.1.5 成员属性设置为私有
① 优点1:将所有成员属性设置为私有,可以自己控制读写权限。
② 优点2:可以通过写权限,检测数据的有效性。
#include <iostream>
using namespace std;
#include <string>
//成员属性设置为私有
//1、可以自己控制读写权限
//2、对于写可以检测数据的有效性
class Person
{
public:
//设置姓名
void setName(string name)
{
m_Name = name;
}
//获取姓名
string getName()
{
return m_Name;
}
//设置年龄
void setAge(int age)
{
if (age < 0 || age>150) //检测数据的有效性
{
cout << age << "输入的年龄有误" << endl;
return;
}
m_Age = age;
}
//获取年龄
int getAge()
{
return m_Age;
}
void setLover(string lover)
{
m_Lover = lover;
}
private:
//姓名 可读可写
string m_Name;
//年龄 可读也可写(年龄要在0-150岁)
int m_Age = 18;//初始年龄为18岁
//情人 只写
string m_Lover;
};
int main()
{
Person p;
p.setName("小帅");
cout << "姓名为:" << p.getName() << endl;
//p.m_Age = 20; //私有权限,不可以改
p.setAge(20);
cout << "年龄为:" << p.getAge() << endl;
//检测数据的有效性
p.setAge(200);
cout << "年龄为:" << p.getAge() << endl;
p.setLover("小美");
//cout << "年龄为:" << p.getLover() << endl; 错误
system("pause");
return 0;
}
/*
姓名为:小帅
年龄为:20
200输入的年龄有误
年龄为:20
请按任意键继续. . .
*/
13.1.6 设计立方体类
案例描述:设计立方体类(Cube),求出立方体的面积和体积,分别用全局函数和成员函数判断两个立方体是否相等。
#include <iostream>
using namespace std;
//立方体类设计
//1、创建立方体类
//2、设计属性
//3、获取立方体面积和体积
//4、分别利用全局函数和成员函数 判断两个立方体是否相等
class Cube
{
public:
//设置长
void setL(int l)
{
m_L = l;
}
//获取长
int getL()
{
return m_L;
}
//设置宽
void setW(int w)
{
m_W = w;
}
//获取宽
int getW()
{
return m_W;
}
//设置高
void setH(int h)
{
m_H = h;
}
//获取高
int getH()
{
return m_H;
}
//获取立方体面积
int calculateS()
{
return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H;
}
//获取立方体体积
int calculateV()
{
return m_L * m_W * m_H;
}
//利用成员函数判断两个立方体是否相等
bool isSameByClass(Cube c) //成员函数只需要传入一个类为参数
{
if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH()) //类内可以访问成员函数:m_L、m_W、m_H
{
return true;
}
else
{
return false;
}
}
private:
int m_L; //长
int m_W; //宽
int m_H; //高
};
//利用全局函数判断,两个立方体是否相等
bool isSame(Cube c1, Cube c2) //值传递会拷贝一份数据,通过引用传递用原始数据,不会拷贝数据
{
if (c1.getL() == c2.getL() && c1.getW() == c2.getW()
&& c1.getH() == c2.getH())
{
return true;
}
else
{
return false;
}
}
int main()
{
//创建立方体对象
Cube c1;
c1.setL(10);
c1.setW(10);
c1.setH(10);
cout << "c1的面积为:" << c1.calculateS() << endl;
cout << "c1的体积为:" << c1.calculateV() << endl;
Cube c2;
c2.setL(10);
c2.setW(10);
c2.setH(11);
bool ret = isSame(c1, c2);
if (ret)
{
cout << "利用全局函数判断:c1和c2是相等的" << endl;
}
else
{
cout << "利用全局函数判断:c1和c2是不相等的" << endl;
}
//利用成员函数判断
ret = c1.isSameByClass(c2);
if (ret)
{
cout << "利用成员函数判断:c1和c2是相等的" << endl;
}
else
{
cout << "利用成员函数判断:c1和c2是不相等的" << endl;
}
system("pause");
return 0;
}
/*
c1的面积为:600
c1的体积为:1000
利用全局函数判断:c1和c2是不相等的
利用成员函数判断:c1和c2是不相等的
请按任意键继续. . .
*/
13.1.7 点和圆的关系
#include <iostream>
using namespace std;
//点和圆关系案例
//点类
class Point
{
public:
//设置x
void setX(int x)
{
m_X = x;
}
//获取x
int getX()
{
return m_X;
}
//设置y
void setY(int y)
{
m_Y = y;
}
//获取y
int getY()
{
return m_Y;
}
private:
int m_X;
int m_Y;
};
//圆类
class Circle
{
public:
//设置半径
void setR(int r)
{
m_R = r;
}
//获取半径
int getR()
{
return m_R;
}
//设置圆心
void setCenter(Point center)
{
m_Center = center;
}
//获取圆心
Point getCenter()
{
return m_Center;
}
private:
int m_R; //半径
Point m_Center; //圆心 //在类中可以让另一个类 作为本类中的成员
};
//判断点和圆关系
void isInCircle(Circle& c, Point& p)
{
//计算两点之间距离 平方
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY()); //c.getCenter()返回的是圆心点center类
//可以调用圆心点center类中的方法
//计算半径的平方
int rDistance = c.getR() * c.getR();
//判断关系
if (distance == rDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
int main()
{
//创建圆
Circle c;
c.setR(10);
Point center;
center.setX(10); //设置点的横坐标
center.setY(0); //设置点的纵坐标
c.setCenter(center); //设置点类传入圆类
//创建点
Point p;
p.setX(10);
p.setY(10);
//判断关系
isInCircle(c, p);
system("pause");
return 0;
}
/*
点在圆上
请按任意键继续. . .
*/
13.2 对象特性
13.2.1 对象的初始化和清理
① 对象的初始化和清理是两个非常重要的安全问题。
② 一个对象或者变量没有初始状态,对其使用后果是未知。
③ 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。
④ C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
⑤ 对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
13.2.1.1 构造函数和析构函数
2.1 构造函数和析构函数的作用
① 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
② 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
2.2 构造函数和析构函数的语法
① 构造函数语法:类名 () {}
- 构造函数,没有返回值也不写void。
- 函数名称与类名相同。
- 构造函数可以有参数,因此可以重载。
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。
② 析构函数语法:~类名(){}
- 析构函数,,没有返回值也不写void。
- 函数名称与类名相同,在名称前加上符号。
- 析构函数不可以有参数,因此不可以发生重载。
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次。
#include <iostream>
using namespace std;
#include <string>
//对象的初始化和清理
//1、构造函数 进行初始化操作
class Person
{
public: //无论是构造函数还是析构函数都是在public作用域下
//1.1、构造函数
//没有返回值 不用写void
//函数名 与类名相同
//构造函数可以有参数,可以发生重载
//创建对象的时候,构造函数会自动调用,而且只调用一次
Person()
{
cout << "Person 构造函数的调用" << endl;
}
/*
如果你不写,编译器会自动创建一个,但是里面是空语句
Person()
{
}
*/
/*
1. 析构函数,没有返回值也不写void。
2. 函数名称与类名相同,在名称前加上符号。
3. 析构函数不可以有参数,不可以发生重载。
4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次。
*/
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现
void test01()
{
Person p; //创建对象的时候,自动调用构造函数
//这个对象p是一个局部变量,是在栈上的数据,test01执行完,释放这个对象
}
int main()
{
//方式一:
test01(); // 析构释放时机在test01运行完前,test01函数运行完后,里面的对象就被释放了
//方式二: //创建对象的时候,自动调用构造函数
Person p; //只有构造函数,没有析构函数,只有main函数结束完前,对象要释放掉了,才会调用析构函数
system("pause");
return 0;
}
/*
Person 构造函数的调用
Person 析构函数的调用
Person 构造函数的调用
请按任意键继续. . .
Person 析构函数的调用
*/
13.2.1.2 构造函数的分类及调用
① 两种分类方式:
- 按参数分为:有参构造和无参构造。
- 按类型分为:普通构造和拷贝构造。
② 三种调用方式:
- 括号法
- 显示法
- 隐式转换法
#include <iostream>
using namespace std;
//1构造函数的分类及调用
//分类
//按照参数分类:无参构造(默认构造) 和 有参构造
class Person
{
public:
//构造函数 编译器默认的构造函数是无参的
Person()
{
cout << "Person 无参构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person 有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person& p) //用引用的方式传进来,不能改变原来的对象的属性,所以用const
{
// 将传入的人人身上的所有属性,拷贝到我身上
cout << "Person 拷贝构造函数的调用" << endl;
age = p.age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int age;
};
//调用
void test01()
{
/*
//1、括号法
Person p1; //默认构造函数调用
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数
cout << "p2的年龄为:" << p2.age << endl;
cout << "p3的年龄为:" << p3.age << endl;
//注意事项1
//调用默认构造函数的时候,不要加()。
//下面这行代码,编译器会认为是一个函数的声明,像void func(),不会认为在创建对象。
//Person p1();
*/
/*
//2、显示法
Person p1; //创建一个对象,这个对象调用的是无参构造
Person p2 = Person(10); //有参构造 将匿名对象起了一个名称p2
Person p3 = Person(p2); //创建一个对象,这个对象调用的是拷贝构造
Person(10); //匿名对象 特点:当前行执行结束后,系统会立即回收匿名对象
cout << "aaaa" << endl; //通过打印时机可以得到:test还没结束,就运行析构函数了
//注意事项2
//不要利用拷贝构造函数 初始化匿名对象 编译器认为 Person(p3) 等价于 Person p3,
//编译器会认为这是一个对象的声明,而上面已经有一个p3了,Person p3 = Person(p2);因此编译器认为重定义了
//Person(p3);
*/
//3、隐式转换法
Person p4 = 10; //相当于 写了 Person p4 = Person(10); 调用有参构造
Person p5 = p4; //调用拷贝构造
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Person 有参构造函数的调用
Person 拷贝构造函数的调用
Person 析构函数的调用
Person 析构函数的调用
请按任意键继续. . .
*/
1、括号法
#include <iostream>
using namespace std;
//1构造函数的分类及调用
//分类
//按照参数分类:无参构造(默认构造) 和 有参构造
class Person
{
public:
//构造函数 编译器默认的构造函数是无参的
Person()
{
cout << "Person 无参构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person 有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person& p) //用引用的方式传进来,不能改变原来的对象的属性,所以用const
{
// 将传入的人人身上的所有属性,拷贝到我身上
cout << "Person 拷贝构造函数的调用" << endl;
age = p.age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int age;
};
//调用
void test01()
{
//1、括号法
Person p1; //默认构造函数调用
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数
cout << "p2的年龄为:" << p2.age << endl;
cout << "p3的年龄为:" << p3.age << endl;
//注意事项1
//调用默认构造函数的时候,不要加()。
//下面这行代码,编译器会认为是一个函数的声明,像void func(),不会认为在创建对象。
//Person p1();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Person 无参构造函数的调用
Person 有参构造函数的调用
Person 拷贝构造函数的调用
p2的年龄为:10
p3的年龄为:10
Person 析构函数的调用
Person 析构函数的调用
Person 析构函数的调用
请按任意键继续. . .
*/
2、显示法
#include <iostream>
using namespace std;
//1构造函数的分类及调用
//分类
//按照参数分类:无参构造(默认构造) 和 有参构造
class Person
{
public:
//构造函数 编译器默认的构造函数是无参的
Person()
{
cout << "Person 无参构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person 有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person& p) //用引用的方式传进来,不能改变原来的对象的属性,所以用const
{
// 将传入的人人身上的所有属性,拷贝到我身上
cout << "Person 拷贝构造函数的调用" << endl;
age = p.age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int age;
};
//调用
void test01()
{
//2、显示法
Person p1; //创建一个对象,这个对象调用的是无参构造
Person p2 = Person(10); //有参构造 将匿名对象起了一个名称p2
Person p3 = Person(p2); //创建一个对象,这个对象调用的是拷贝构造
Person(10); //匿名对象 特点:当前行执行结束后,系统会立即回收匿名对象
cout << "aaaa" << endl; //通过打印时机可以得到:test还没结束,就运行析构函数了
//注意事项2
//不要利用拷贝构造函数 初始化匿名对象 编译器认为 Person(p3) 等价于 Person p3,
//编译器会认为这是一个对象的声明,而上面已经有一个p3了,Person p3 = Person(p2);因此编译器认为重定义了
//Person(p3);
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Person 无参构造函数的调用
Person 有参构造函数的调用
Person 拷贝构造函数的调用
Person 有参构造函数的调用
Person 析构函数的调用
aaaa
Person 析构函数的调用
Person 析构函数的调用
Person 析构函数的调用
请按任意键继续. . .
*/
3、隐式转换法
#include <iostream>
using namespace std;
//1构造函数的分类及调用
//分类
//按照参数分类:无参构造(默认构造) 和 有参构造
class Person
{
public:
//构造函数 编译器默认的构造函数是无参的
Person()
{
cout << "Person 无参构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person 有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person& p) //用引用的方式传进来,不能改变原来的对象的属性,所以用const
{
// 将传入的人人身上的所有属性,拷贝到我身上
cout << "Person 拷贝构造函数的调用" << endl;
age = p.age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int age;
};
//调用
void test01()
{
//3、隐式转换法
Person p4 = 10; //相当于 写了 Person p4 = Person(10); 调用有参构造
Person p5 = p4; //调用拷贝构造
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Person 有参构造函数的调用
Person 拷贝构造函数的调用
Person 析构函数的调用
Person 析构函数的调用
请按任意键继续. . .
*/
13.2.1.3 拷贝构造函数调用时机
① C++中拷贝构造函数调用时机通常有三种情况。
- 使用一个已经创建完毕的对象来初始化一个新对象。
- 值传递的方式给函数参数传值。
- 以值方式返回局部对象。
#include <iostream>
using namespace std;
//拷贝构造函数调用时机
//1、使用一个已经创建完毕的对象来初始化一个新对象
//2、值传递的方式给函数参数传值
//3、值方式返回局部对象
class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}
Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}
Person(const Person& p)
{
m_Age = p.m_Age;
cout << "Person 拷贝构造函数调用" << endl;
}
~Person()
{
cout << "Person 析构函数调用" << endl;
}
int m_Age;
};
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << endl;
}
//2、值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p;
doWork(p); //实参传给形参的时候,会调用拷贝构造函数,这个是值传递,是一个临时的副本
//拷贝出去的p和原来的p 不是一个p
}
//3、值方式返回局部对象
Person doWork2() //返回值类型为Person对象
{
Person p1; //局部对象
cout << (int*)&p1 << endl;
return p1; //以值的方式返回一个拷贝的对象给外部,拷贝出一个对象p1'与原对象p1不一样,调用拷贝构造函数
//程序运行结束,释放原p1,调用析构函数
}
void test03()
{
Person p = doWork2(); //这里没有调用拷贝构造函数,直接用p接收拷贝对象p1’
cout << (int*)&p << endl;
//程序运行结束,释放拷贝的对象p1',调用析构函数
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
/*
Person 有参构造函数调用
Person 拷贝构造函数调用
p2的年龄为:20
Person 析构函数调用
Person 析构函数调用
Person 默认构造函数调用
Person 拷贝构造函数调用
Person 析构函数调用
Person 析构函数调用
Person 默认构造函数调用
012FF88C
012FF88C
Person 析构函数调用
请按任意键继续. . .
*/
13.2.1.4 构造函数调用规则
① 默认情况下,C++编译器至少给一个类添加3个函数。
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
② 构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造。
- 如果用户定义拷贝函数,C++不会再提供其他构造函数。
③ 巧记法,如下图所示,如果定义中间的,上面的就默认不定义了,下面的默认定义。
1. 调用定义的拷贝构造函数
#include <iostream>
using namespace std;
//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)
class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}
Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}
Person(const Person & p)
{
m_Age = p.m_Age;
cout << "Person 拷贝构造函数调用" << endl;
}
~Person()
{
cout << "Person 析构函数调用" << endl;
}
int m_Age;
};
void test01()
{
Person p;
p.m_Age = 18;
Person p2(p);
cout << "p2的年龄:" << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Person 默认构造函数调用
Person 拷贝构造函数调用
p2的年龄:18
Person 析构函数调用
Person 析构函数调用
请按任意键继续. . .
*/
2. 调用默认的拷贝构造函数
#include <iostream>
using namespace std;
//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)
class Person
{
public:
Person()
{
cout << "Person 默认构造函数调用" << endl;
}
Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}
//编译器自动提高拷贝构造函数
~Person()
{
cout << "Person 析构函数调用" << endl;
}
int m_Age;
};
void test01()
{
Person p;
p.m_Age = 18;
Person p2(p); //调用编译器默认的拷贝构造函数会把p的所有属性拷贝过来
cout << "p2的年龄:" << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Person 默认构造函数调用
p2的年龄:18
Person 析构函数调用
Person 析构函数调用
请按任意键继续. . .
*/
3. 调用定义的有参构造函数
#include <iostream>
using namespace std;
//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)
class Person
{
public:
Person(int age)
{
m_Age = age;
cout << "Person 有参构造函数调用" << endl;
}
//编译器自动提高拷贝构造函数
~Person()
{
cout << "Person 析构函数调用" << endl;
}
int m_Age;
};
void test02()
{
Person p; //如果写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造构造
//由于没有默认构造函数,所以报错
Person p2(p);
}
int main()
{
test02();
system("pause");
return 0;
}
4. 调用定义的拷贝构造函数
#include <iostream>
using namespace std;
//构造函数的调用规则
//1、创建一个类,C+=编译器会给每个类都添加至少3个函数
//默认构造 (空实现)
//析构函数 (空实现)
//拷贝构造 (值拷贝)
class Person
{
public:
//如果写了拷贝构造函数,编译器就不再提供其他普通构造函数
Person(const Person& p)
{
m_Age = p.m_Age;
cout << "Person 拷贝构造函数调用" << endl;
}
~Person()
{
cout << "Person 析构函数调用" << endl;
}
int m_Age;
};
void test01()
{
Person p; //没有默认构造函数,报错
Person(10); //没有有参构造函数,报错
Person p2(p);
cout << "p2的年龄:" << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
13.2.1.5 深拷贝与浅拷贝
① 浅拷贝:简单的赋值拷贝操作。
② 深拷贝:在堆区重新申请空间,进行拷贝操作。
③ 浅拷贝,如下图所示,带来的问题就是堆区的内存重复释放。
④ 深拷贝,如下图所示,在堆区自己创建一份内存,可以避免堆区的内存重复释放。
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height); //把数据创建在堆区,用指针接收new创建的地址
cout << "Person的有参构造函数调用" << endl;
}
//自己实现拷贝函数 解决浅拷贝带来的问题
Person(const Person& p)
{
cout << "Person 拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; 编译器默认实现就是这行代码,默认执行的是浅拷贝
//浅拷贝带来的问题就是堆区的内存重复释放
// 深拷贝操作,在堆区自己创建一份内存
m_Height = new int(*p.m_Height);
}
~Person()
{
//析构代码,将堆区开辟数据做释放操作
cout << "Person的析构函数调用" << endl;
if (m_Height != NULL)
{
delete m_Height; //释放堆区数据
}
}
int m_Age;
int * m_Height;
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_Age << "身高为:" << * p1.m_Height << endl; //指针通过解引用获得数据
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << "身高为:" << * p2.m_Height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Person的有参构造函数调用
p1的年龄为:18身高为:160
Person 拷贝构造函数调用
p2的年龄为:18身高为:160
Person的析构函数调用
Person的析构函数调用
请按任意键继续. . .
*/
13.2.1.6 初始化列表
① C++提供了初始化列表语法,用来初始化属性。
② 语法:构造函数(): 属性1(值1),属性2(值2),...,()
4.1 传统初始化操作
#include <iostream>
using namespace std;
class Person
{
public:
//传统初始化操作
Person(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
Person p(10, 20, 30);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
m_A:10
m_B:20
m_C:30
请按任意键继续. . .
*/
4.2 灵活初始化操作
#include <iostream>
using namespace std;
class Person
{
public:
/*
构造函数型的初始化操作
固定初始化10、30、40
Person():m_A(10),m_B(30),m_C(40)
{
}
int m_A;
int m_B;
int m_C;
*/
//可以灵活的初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
Person p(30, 20, 10);
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
m_A:30
m_B:20
m_C:10
请按任意键继续. . .
*/
13.2.1.7 类对象作为类成员
① C++类中的属性、方法称为成员。
② C++类中的成员可以是另一个类的对象,称该成员为对象成员。
③ B类中有对象A作为成员,A为对象成员,那么当创建B对象时,A与B的构造和析构的顺序是:
- 当其他类对象作为本类成员,构造时候先构造其他类对象,在构造自身。
- 当其他类对象作为本类成员,析构的顺序与构造相反,想析构自身,再析构其他类对象。
#include <iostream>
using namespace std;
//手机类
class Phone
{
public:
Phone(string pName)
{
cout << "Phone的构造函数调用" << endl;
m_PName = pName;
}
~Phone()
{
cout << "Phone的析构代码函数调用" << endl;
}
string m_PName;
};
//人类
class Person
{
public:
//m_Phone(pName) 中m_Phone为phone对象,此语句类似于隐式转换法 Phone m_Phone = pName
Person(string name, string pName) :m_Name(name), m_Phone(pName) //掉用的是灵活初始化列表
{
cout << "Person的构造函数调用" << endl;
}
~Person()
{
cout << "Person的析构代码函数调用" << endl;
}
//姓名
string m_Name;
//手机
Phone m_Phone;
};
//当其他类对象作为本类成员,构造时候先构造其他类对象,在构造自身。
//当其他类对象作为本类成员,析构的顺序与构造相反,想析构自身,再析构其他类对象
void test01()
{
Person p("张三", "苹果MAX");
cout << p.m_Name << "m_A:" << p.m_Phone.m_PName << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Phone的构造函数调用
Person的构造函数调用
张三m_A:苹果MAX
Person的析构代码函数调用
Phone的析构代码函数调用
请按任意键继续. . .
*/
13.2.1.8 静态成员
① 静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。
② 静态成员分为:
- 静态成员变量
-- 所有对象共享同一份数据
-- 在编译阶段分配内存
-- 类内声明,类外初始化
- 静态成员函数
-- 所有对象共享同一个函数
-- 静态成员函数只能访问静态成员变量
③ 调用静态成员函数有两种方法:
- 通过对象调用
- 通过类名调用
13.2.2 C++对象模型和this指针
静态成员变量
#include<iostream>
using namespace std;
//静态成员变量
class Person
{
public:
//1、所有对象都共享同一份数据
//2、编译阶段就分配内存
//3、类内声明,类外初始化操作
static int m_A;
//静态成员变量也是有访问权限的
private:
static int m_B;
};
int Person::m_A = 100;
void test01()
{
Person p;
cout << p.m_A << endl;
Person p2;
p2.m_A = 200;
//100 ? 200,共享同一份数据,所以p.m_A为200
cout << p.m_A << endl;
}
void test02()
{
//静态成员变量 不属于某个对象上,所有对象都共享同一份数据
//因此静态成员变量有两种访问方式
//1、通过对象进行访问
Person p;
cout << p.m_A << endl;
//2、通过类名进行访问
cout << Person::m_A << endl;
//cout << Person::m_B << endl; //报错,私有作用域,出了类是不可以访问的
}
int main()
{
test01();
test02();
system("pause");
}
/*
100
200
200
200
请按任意键继续. . .
*/
6.2 静态成员函数
#include <iostream>
using namespace std;
//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class Person
{
public:
//静态成员函数
static void func()
{
m_A = 100; //静态成员函数可以访问静态成员变量,这个数据是共享的,只有一份,所以不需要区分哪个对象的。
//m_B = 200; //静态成员函数不可以访问非静态成员变量,无法区分到底是哪个对象的m_B属性,非静态成员变量属于特定的对象上面
cout << "static void func调用" << endl;
}
static int m_A; //静态成员变量
int m_B; //非静态成员变量
//静态成员函数也是有访问权限的
private:
static void func2()
{
cout << "static void func2调用" << endl;
}
};
int Person::m_A = 0;
//有两种访问方式
void test01()
{
//1、通过对象访问
Person p;
p.func();
//2、通过类名访问
Person::func(); //静态成员函数,所有对象共享同一个函数,可以直接通过类名访问。
//Person::func2(); //类外访问不到私有静态成员函数
}
int main()
{
test01();
system("pause");
return 0;
}
/*
static void func调用
static void func调用
请按任意键继续. . .
*/
13.2.2.1 成员变量和成员函数分开存储
① 在C++中,类内对的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。
#include <iostream>
using namespace std;
class Person01
{
public:
};
class Person02
{
public:
int m_A; //非静态成员变量 属于类的对象上
static int m_B; //静态成员变量 不属于类对象上
//void fun(){} //非静态成员函数 不属于类对象上
static void func2() {} //静态成员函数 不属于类的对象上
};
int Person02::m_B = 0;
void test01()
{
Person01 p;
//空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存空间
cout << "size of p = " << sizeof(p) << endl;
}
void test02()
{
Person02 p2;
cout << "size of p2 = " << sizeof(p2) << endl; //通过打印内存空间大小,检测静态成员变量、非静态成员函数等在不在对象内存上....
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
/*
size of p = 1
size of p2 = 4
请按任意键继续. . .
*/
13.2.2.2 this指针概念
① 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码。
② C++通过提供特殊的对象指针,this指针指向被调用的成员函数所属的对象。
③ this指针是隐含每一个非静态成员函数内的一种指针。
④ this指针不需要定义,直接使用即可。
⑤ this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分。
- 在类的非静态成员函数中返回对象本身,可使用return * this。
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//this指针指向的是被调用的成员函数所属的对象
this->age = age; //当下面实例化对象p1在调用,this就指向p1
//用this指针的时候,可以该变量与形参命名相同,但是编译器会认为两个不同
//如果这里是 age = age;那么编译器会将这两个age和上面的形参age当做同一个age,因此age并没有赋值
}
//如果用值的方式返回,Person PersonAddAge(Person& p){},它返回的是本体拷贝的对象p',而不是本体p
Person& PersonAddAge(Person& p) //要返回本体的时候,要用引用的方式返回
{
this->age += p.age; //this->age为调用对象的age
//this指向p2的指针,而*this指向的就是p2这个对象本体
return *this;
}
int age;
};
//1、解决名称冲突
void test01()
{
Person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
}
//2、返回对象本身用*this
void test02()
{
Person p1(10);
Person p2(10);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄为:" << p2.age << endl;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
/*
p1的年龄为:18
p2的年龄为:40
请按任意键继续. . .
*/
13.2.2.3 空指针访问成员函数
① C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针。
② 如果用到this指针,需要加以判断保证代码的健壮性。
#include <iostream>
using namespace std;
class Person
{
public:
void showClassName()
{
cout << "this is Person class" << endl;
}
/*
void showPersonAge()
{
//报错原因是传入的指针是为NULL
cout << "age= " << m_Age << endl; //默认m_Age是this->m_Age
}
*/
void showPersonAge()
{
if (this == NULL)
{
return; //为空的时候直接退出
}
cout << "age= " << this->m_Age << endl;
}
int m_Age;
};
void test01()
{
Person* p = NULL;
p->showClassName();
p->showPersonAge();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
this is Person class
请按任意键继续. . .
*/
13.2.2.4 const修饰成员函数
① 常函数:
- 成员函数后加const后我们称这个函数为常函数。
- 常函数内不可以修改成员属性。
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改。
② 常对象:
- 声明对象前加const称该对象为常对象。
- 常对象只能调用常函数。
#include <iostream>
using namespace std;
class Person
{
public:
//this指针的本质 是指针常量 指针的指向是不可以修改的,即Person * const this
//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改,即void showPerson() const 使得 const Person * const this
void showPerson() const //当加了一个const
{
//m_A = 100; //相当于 this->m_A;,由于加了一个const,所以指针指向的值不可以更改
//this = NULL; //this指针不可以修改指针的指向的
this->m_B = 100; //加了mutable就可以修改this指向的值了
}
void func()
{
m_A = 100;
}
int m_A;
mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值,加上关键字mutable
};
void test01()
{
Person p;
p.showPerson();
}
//常对象
void test02()
{
const Person p; //在对象前加const,变为常对象
//p.m_A = 100; //常对象不可以修改普通变量
p.m_B = 100; //m_B是特殊值,在常对象下也可以修改
//常对象只能调用常函数
p.showPerson();
//p.func(); //常对象 不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main()
{
test01();
system("pause");
return 0;
}
/*
请按任意键继续. . .
*/
13.3 友元
13.3.1 友元简介
① 生活中你的家有客厅(Public),有你的卧室(Private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的好闺蜜进去。
② 在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。
③ 友元的目的就是让一个函数或者类访问另一个类中私有成员。
④ 友元的关键字为 friend。
⑤ 友元的三种实现:
- 全局函数做友元。
- 类做友元。
- 成员函数做友元。
#include <iostream>
using namespace std;
//建筑物类
class Buiding
{
//goodfriend全局函数是Buiding类好朋友,可以访问Buiding中私有成员
friend void goodfriend(Buiding* buiding);
public:
Buiding() //构造函数 赋初值
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;
};
//全局函数
void goodfriend(Buiding *buiding)
{
cout << "好基友全局函数 正在访问:" << buiding->m_SittingRoom << endl;
cout << "好基友全局函数 正在访问:" << buiding->m_BedRoom << endl;
}
void test01()
{
Buiding building;
goodfriend(&building);
}
int main()
{
test01();
system("pause");
return 0;
}
/*
好基友全局函数 正在访问:客厅
好基友全局函数 正在访问:卧室
请按任意键继续. . .
*/
13.3.2 友元类
#include <iostream>
using namespace std;
//类做友元
class Building; //声明
class GoodGay
{
public:
GoodGay();
void visit(); //参观函数 访问Building中的属性
Building* building;
};
class Building
{
//GoodGay类是本类的好朋友,可以访问本类的私有成员
friend class GoodGay;
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
//类外写成员函数,写类名的作用域
//Building构造函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
//GoodGay构造函数
GoodGay::GoodGay()
{
//创建建筑物对象
building = new Building; //这里会调用Building的构造函数
//new是创建什么样的数据类型,就返回什么样的数据类型的指针
}
void GoodGay::visit()
{
cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg; //创建一个GoodGay的对象,会调用GoodGay构造函数,会创建一个building,然后调用building构造函数
gg.visit();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
好基友类正在访问:客厅
好基友类正在访问:卧室
请按任意键继续. . .
*/
13.3.3 成员函数做友元
#include <iostream>
using namespace std;
class Building;
class GoodGay
{
public:
GoodGay();
void visit(); //让visit函数可以访问Building中私有成员
void visit2(); //让visit2函数不可以访问Buildinng中私有成员
Building* building;
};
class Building
{
//告诉编译器,GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
// 类外实现构造函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
// 类外实现成员函数
void GoodGay::visit()
{
cout << "visit 函数正在访问" << building->m_SittingRoom << endl;
cout << "visit 函数正在访问" << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit2 函数正在访问" << building->m_SittingRoom << endl;
//cout << "visit 函数正在访问" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
visit 函数正在访问客厅
visit 函数正在访问卧室
visit2 函数正在访问客厅
请按任意键继续. . .
*/
13.4 运算符重载
1. 运算符重载简介
① 运算符重载:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
② 对于内置的数据类型的表达式的运算符是不可能改变的。
2. 加号运算符重载
① 加号运算符作用:实现两个自定义数据类型相加的运算。
#include <iostream>
using namespace std;
//加号运算符重载
class Person
{
public:
//1、成员函数重载+号
Person operator+(Person& p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
int m_A;
int m_B;
};
/*
//全局函数重载+号
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
*/
//函数重载的版本
Person operator+(Person& p1, int num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//成员函数重载本质调用
//Person p3 = p1.operator+(p2);
//全局函数重载本质调用
//Person p3 = operator+(p1,p2);
Person p3 = p1 + p2; //重载本质被简化后的形式
//运算符重载,也可以发生函数重载
Person p4 = p1 + 10; //Person + int
cout << "p3.m_A:" << p3.m_A << endl;
cout << "p3.m_B:" << p3.m_B << endl;
cout << "p4.m_A:" << p4.m_A << endl;
cout << "p4.m_B:" << p4.m_B << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
p3.m_A:20
p3.m_B:20
p4.m_A:20
p4.m_B:20
请按任意键继续. . .
*/
3. 左移运算符重载
① 左移运算符重载:可以输出自定义数据类型。
② 重载左移运算符配合友元可以实现自定义数据类型。
#include <iostream>
using namespace std;
//左移运算符
class Person
{
friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
/*
//利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本 p << cout
//成员函数不能利用重载<<运算符,因为无法实现cout在左侧
//当不知道返回值是什么类型的时候,可以先写个void,以后再改
void operator<<(Person& p)
{
}
*/
private:
int m_A;
int m_B;
};
//只能利用全局函数重载左移运算符
//如果返回类型为void,那么就无法无限追加,也没有办法在后面添加换行符
ostream & operator<<(ostream &cout, Person &p) //本质 operator << (cout , p) , 简化 cout << p
//cout是别名,这里可以取out、kn...
//cout为ostream输出流数据类型
{
cout << "m_A= " << p.m_A << " m_B=" << p.m_B;
return cout;
}
void test01()
{
Person p(10,10);
cout << p << " hello world" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
m_A= 10 m_B=10 hello world
请按任意键继续. . .
*/
4. 递增运算符
① 递增运算符重载:通过重载递增运算符,实现自己的整型数据。
② 前置递增返回的是引用,后置递增返回的是值。
#include <iostream>
using namespace std;
//重载递增运算符
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载前置++运算符,返回引用是为了一直对一个数据进行递增操作,而返回值并不是一直对一个数据进行递增操作
MyInteger& operator++()
{
//先进行++运算
m_Num++;
//再将自身做一个返回
return *this; //把自身做一个返回
}
//重载后置++运算符 int代表占位参数,可以用于区分前置和后置递增
//后置递增不能返回引用,因为temp是局部变量,如果返回temp,当程序运行完后变量就释放了,再调用temp就是非法操作了
MyInteger operator++(int)
{
//先记录当时结果
MyInteger temp = *this;
//后 递增
m_Num++;
//最后将记录结果做返回
return temp;
}
private:
int m_Num;
};
//只能利用全局函数重载左移运算符
ostream & operator<<(ostream &cout, MyInteger myint) //本质 operator << (cout , p) , 简化 cout << p
//cout是别名,这里可以取out、kn...
//cout为ostream输出流数据类型
{
cout << myint.m_Num;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++(++myint) << endl;
cout << myint << endl;
}
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main()
{
test01();
test02();
/*
int a = 0;
cout << ++(++a) << endl; //运行结果为2
cout << a << endl; //运行结果为2,表示一直对一个数据进行递增
*/
system("pause");
return 0;
}
/*
2
2
0
1
请按任意键继续. . .
*/
5. 赋值运算符
① C++编译器至少给一个类添加4个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符operator=,对属性进行值拷贝
- 如果类中有属性指向堆区,做赋值操作时也会出现浅拷贝问题。
#include <iostream>
using namespace std;
//重载赋值运算符
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//重载 赋值运算符
//如果返回的是值,而不是引用,是创建一个拷贝函数,返回的是一个副本,而不是自身
Person& operator=(Person& p)
{
//编译器默认是提供浅拷贝
//m_Age = p.m_Age;
//浅拷贝带来的问题是,当创建数据在堆区时,析构代码导致内存重复释放,报错
//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝
m_Age = new int(*p.m_Age);
//返回对象本身
return *this;
}
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(23);
p3 = p2 = p1; //赋值操作
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main()
{
test01();
/*
int a = 10;
int b = 20;
int c = 30;
c = b = a;
cout << "a= " << a << endl;
cout << "b= " << b << endl;
cout << "c= " << c << endl;
*/
system("pause");
return 0;
}
/*
p1的年龄为:18
p2的年龄为:18
p3的年龄为:18
请按任意键继续. . .
*/
6. 关系重载运算符
#include <iostream>
using namespace std;
#include<string>
//重载关系运算符
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
//重载 == 号
bool operator==(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
bool operator!=(Person& p)
{
if (this->m_Name != p.m_Name && this->m_Age != p.m_Age)
{
return true;
}
return false;
}
string m_Name;
int m_Age;
};
void test01()
{
Person p1("Tom", 17);
Person p2("Jerry", 18);
if (p1 == p2)
{
cout << "p1和p2是相等的!" << endl;
}
else
{
cout << "p1和p2是不相等的!" << endl;
}
if (p1 != p2)
{
cout << "p1和p2是不相等的!" << endl;
}
else
{
cout << "p1和p2是相等的!" << endl;
}
}
int main()
{
test01();
system("pause");
return 0;
}
/*
p1和p2是不相等的!
p1和p2是不相等的!
请按任意键继续. . .
*/
7. 函数调用运算符重载
① 函数调用运算符()也可以重载。
② 由于重载后使用的方式非常像函数的调用,因此称为仿函数。
③ 仿函数没有固定写法,非常灵活。
#include <iostream>
using namespace std;
#include<string>
//函数调用运算符重载
//打印输出类
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
//正常函数
void MyPrint02(string test)
{
cout << test << endl;
}
void test01()
{
MyPrint myPrint;
myPrint("hello world");
}
//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int ret = myadd(100,100);
cout << "ret = " << ret << endl;
//匿名函数对象
cout << MyAdd()(100, 100) << endl;
// MyAdd()为创建一个匿名对象,匿名对象的特点为当前行执行完立即释放
}
int main()
{
test01();
MyPrint02("hello world"); //由于使用起来非常类似于函数调用,因此称为仿函数
test02();
system("pause");
return 0;
}
/*
hello world
hello world
ret = 200
200
请按任意键继续. . .
*/
13.5 继承
① 继承是面向对象的三大特性之一。
② 定义类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这个时候,就可以考虑利用继承技术,减少重复代码。
1. 继承基本语法
1.1 普通实现
#include <iostream>
using namespace std;
#include<string>
//打印输出类
class Java
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
class Python
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
class CPP
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "........................" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "........................" << endl;
cout << "C++下载视频页面如下:" << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Java下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
Java学科视频
........................
Python下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
Python学科视频
........................
C++下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
C++学科视频
请按任意键继续. . .
*/
1.2 继承实现
#include <iostream>
using namespace std;
#include<string>
//打印输出类
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++....(公共分类列表)" << endl;
}
};
// 继承的好处:减少重复代码
// 语法:class 子类:继承方式 父类
// 子类 也称为 派生类
// 父类 也称为 基类
//Java页面
class Java:public BasePage //继承了BasePage,把BasePage里面的内容全部拿到手了
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
//Python页面
class Python :public BasePage //继承了BasePage,把BasePage里面的内容全部拿到手了
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP :public BasePage //继承了BasePage,把BasePage里面的内容全部拿到手了
{
public:
void content()
{
cout << "CPP学科视频" << endl;
}
};
void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "........................" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "........................" << endl;
cout << "C++下载视频页面如下:" << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Java下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
Java学科视频
........................
Python下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
Python学科视频
........................
C++下载视频页面如下:
首页、公开课、登陆、注册...(公共头部)
帮助中心、交流合作、站内地图...(公共底部)
Java、Python、C++....(公共分类列表)
CPP学科视频
请按任意键继续. . .
*/
2. 继承方式
① 继承的语法:class 子类:继承方式 父类
② 继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
③ 不同的继承方式,父类中的变量被继承后,权限相应的得到了改变,如下图所示。
#include <iostream>
using namespace std;
#include<string>
//打印输出类
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1:public Base1
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中依然是公共权限
m_B = 10; //父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10; //父类中的私有权限成员 子类访问不到
}
};
void test01()
{
Son1 s1;
s1.m_A = 100; //公共权限,类内能访问,类外也能访问
//s1.m_B = 100; //保护权限,类内能访问,类外不能访问
}
//保护继承
class Son2:protected Base1
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中变为保护权限
m_B = 10; //父类中的保护权限成员 到子类中依然是保护权限
//m_C = 10; //父类中的私有权限成员 子类访问不到
}
};
void test02()
{
Son2 s2;
//s2.m_A = 100; //保护权限,类内能访问,类外不能访问
//s2.m_B = 100; //保护权限,类内能访问,类外不能访问
}
//私有继承
class Son3:private Base1
{
public:
void func()
{
m_A = 10; //父类中的公共权限成员 到子类中变为私有权限
m_B = 10; //父类中的保护权限成员 到子类中变为私有权限
//m_C = 10; //父类中的私有权限成员 子类访问不到
}
};
void test03()
{
Son3 s3;
//s3.m_A = 100; //私有权限,类内能访问,类外不能访问
//s3.m_B = 100; //私有权限,类内能访问,类外不能访问
}
int main()
{
system("pause");
return 0;
}
/*
请按任意键继续. . .
*/
3. 继承中的对象模型
3.1 查询继承对象所占内存
#include <iostream>
using namespace std;
#include<string>
//继承中的对象模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son:public Base
{
int m_D;
};
//利用开发人员命令提示工具查看对象模型
//跳转盘符→F:
//跳转文件路径→cd 具体路径下
//查看命令
//cl /d1 reportSingleClassLayout查看的类名 "文件名"
void test01()
{
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
cout << "size of Son =" << sizeof(Son) << endl; //16个字节,父类3个int一个12个字节,字节1个int4个字节
}
int main()
{
test01();
system("pause");
return 0;
}
/*
size of Son =16
按任意键继续. . .
*/
3.2 VS自带开发工具查询
① 首先打开visio studio的开发人员命令工具,如下图所示。
② 查询visio studio的项目所在地址。
③ "输入跳转盘符,例如,C:"->"输入项目所在地的地址"->"输入dir(查询项目中文件信息)"
② 输入:cl(空格)/d1 reportSingleClassLayout查看的类名 "文件名",可以查看类所占内存空间大小。
4. 继承构造和析构顺序
① 继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造顺序相反,先调用子类析构函数,再调用父类析构函数。
#include <iostream>
using namespace std;
#include<string>
//继承中的构造和析构顺序
class Base
{
public:
Base()
{
cout << "Base构造函数!" << endl;
}
~Base()
{
cout << "Base析构函数!" << endl;
}
};
//
class Son:public Base
{
public:
Son()
{
cout << "Son构造函数!" << endl;
}
~Son()
{
cout << "Son析构函数!" << endl;
}
};
void test01()
{
//Base b; //创建父类对象只有父类的构造函数、析构函数
//继承中的构造和析构顺序如下:
//先构造父类、再构造子类,析构的顺序与构造的顺序相反
Son s;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Base构造函数!
Son构造函数!
Son析构函数!
Base析构函数!
请按任意键继续. . .
*/
5. 同名成员处理
① 子类对象可以直接访问到子类中同名成员。
② 子类对象加作用域可以访问到父类同名成员。
③ 当子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数(有参、无参),加作用域才可以访问到父类中同名函数。
#include <iostream>
using namespace std;
#include<string>
//继承中同名成员处理
class Base
{
public:
Base()
{
m_A = 100;
}
int m_A;
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
};
class Son:public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son - func()调用" << endl;
}
int m_A;
};
//同名成员属性处理方式
void test01()
{
Son s;
cout << "Son 下 m_A=" << s.m_A << endl;
//如果通过子类对象访问到父类中同名成员,需要加作用域
cout << "Base 下 m_A=" << s.Base::m_A << endl;
}
//同名成员函数处理方式
void test02()
{
Son s;
s.func(); //直接调用 调用时子类中的同名成员
//调用父类中同名成员函数
s.Base::func();
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
s.Base::func(100);
}
//同名成员函数处理
int main()
{
test01();
test02();
system("pause");
return 0;
}
/*
Son 下 m_A=200
Base 下 m_A=100
Son - func()调用
Base - func()调用
Base - func(int a)调用
请按任意键继续. . .
*/
6. 同名静态成员处理
① 静态成员和非静态成员出现同名,处理方式一致:
- 访问子类同名成员,直接访问
- 访问父类同名成员,需要加作用域
② 加上static关键字后,成员发生变化,成员变成静态成员。
③ 静态成员变量特点:
- 所有对象都共享同一份数据。
- 编译阶段就分配内存。
- 类内声明,类外初始化。
④ 静态成员函数特点:
- 只能访问静态成员变量,不能访问非静态成员变量。
- 所有对象都共享同一份函数实例。
#include <iostream>
using namespace std;
//继承中同名静态成员处理方式
class Base
{
public:
static int m_A; //静态成员类内声明,类外初始化
static void func()
{
cout << "Base - static func()" << endl;
}
static void func(int a)
{
cout << "Base - static func(int a)" << endl;
}
};
int Base::m_A=100;
class Son:public Base
{
public:
static int m_A;
static void func()
{
cout << "Son - static void func()" << endl;
}
};
int Son::m_A = 200;
//同名静态成员属性
void test01()
{
//1、通过对象访问
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//2、通过类名访问
cout << "Son 下 m_A = "<< Son::m_A << endl;
//第一个::代表通过类名方式访问 第二个::代表访问父类作用域
cout << "Base 下 m_A= "<< Son::Base::m_A << endl;
}
void test02()
{
//1、通过对象访问
Son s;
s.func();
s.Base::func();
//2、通过类名访问
Son::func();
Son::Base::func();
//子类出现和父类同名静态成员函数,也会隐藏掉父类中所有同名成员函数
//如何想访问父类中被隐藏同名成员,需要加作用域
Son::Base::func(100);
}
//同名成员函数处理
int main()
{
test01();
test02();
system("pause");
return 0;
}
/*
Son 下 m_A = 200
Base 下 m_A = 100
Son 下 m_A = 200
Base 下 m_A= 100
Son - static void func()
Base - static func()
Son - static void func()
Base - static func()
Base - static func(int a)
请按任意键继续. . .
*/
7. 多继承语法
① C++运行一个类继承多个类。
② 语法:class 子类:继承方式 父类1,继承方式 父类2,.....
③ 多继承可能会引发父类中有同名成员出现,需要加作用域区分。
④ C++实际开发中不建议用多继承。
#include <iostream>
using namespace std;
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
//子类 需要继承Base1和Base2
//语法:class 子类:继承方式 父类1,继承方式 父类2,.....
class Son:public Base1,public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test01()
{
Son s;
cout << "sizeof(Son):" << sizeof(s) << endl;
//当父类中出现同名成员,需要加作用域区分
cout << "Base1::m_A = " << s.Base1::m_A << endl;
cout << "Base2::m_A = " << s.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
sizeof(Son):16
Base1::m_A = 100
Base2::m_A = 200
请按任意键继续. . .
*/
8. 菱形继承
8.1 菱形继承简介
① 菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承两个派生类
- 这种继承被称为菱形继承
② 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据是,就会产生二义性。
③ 草泥马继承自动物的数据继承了两份,其实我们应当清楚,这份数据我们只需要一份就可以。
④ 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。
⑤ 利用虚继承可以解决菱形继承问题。
8.1 菱形继承普通方式
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
int m_Age;
};
//羊类
class Sheep:public Animal{};
//驼类
class Tuo:public Animal{};
//羊驼类
class SheepTuo:public Sheep,public Tuo{};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//当出现菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_Age="<< st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age=" << st.Tuo::m_Age << endl;
//这份数据我们知道 只有一份就可以,菱形继承导致数据有两份,资源浪费
}
int main()
{
test01();
system("pause");
return 0;
}
/*
st.Sheep::m_Age=18
st.Tuo::m_Age=28
请按任意键继续. . .
*/
8.2 菱形继承虚继承
8.3.1 菱形继承虚继承方式
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
int m_Age;
};
//利用虚继承 解决菱形继承的问题
//继承之前 加上关键字 virtual 变成 虚继承
//虚继承后,Animal类 称为 虚基类
//羊类
class Sheep:virtual public Animal{};
//驼类
class Tuo:virtual public Animal{};
//羊驼类
class SheepTuo:public Sheep,public Tuo{};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//当出现菱形继承,两个父类拥有相同数据,需要加以作用域区分
cout << "st.Sheep::m_Age="<< st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age=" << st.Tuo::m_Age << endl;
//虚继承,多产生一种输出方式(因为上面两个是同一份数据,所以可以不加作用域来区分了)
cout << "st.m_Age=" << st.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
st.Sheep::m_Age=28
st.Tuo::m_Age=28
st.m_Age=28
请按任意键继续. . .
*/
8.3.2 菱形继承虚继承VS查询
① Sheep类和Tuo类继承的都是vbptr,vbptr为虚基类继承指针。
- v - virtual
- b - base
- ptr = pointer
② 菱形继承并不是继承两份数据,而是继承两份指针,这两个指针会分别指向虚基类表,虚基类表中会记录偏移量,加上这个偏移量,会指向这个唯一的数据。
13.6 多态
1. 多态基本语法
① 多态是C++面向对象三大特性之一。
② 多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名。
- 动态多态:派生类和虚函数实现运行时多态。
③ 静态多态和动态多态区别:
- 静态多态的函数地址早绑定,编译阶段确定函数地址。
- 动态多态的函数地址晚绑定,运行阶段确定函数地址。
④ 多态满足条件:
- 有继承关系
- 子类重写父类中的虚函数
⑤ 多态使用条件:
- 父类指针或引用指向子类对象
⑥ 重写:函数返回值类型、函数名、参数列表都完全一致称为重写。
2. 多态地址绑定
2.1 地址早绑定
#include <iostream>
using namespace std;
//多态
//动物类
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &animal) // Animal & animal = cat
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
}
int main()
{
test01();
system("pause");
return 0;
}
/*
动物在说话
请按任意键继续. . .
*/
2.2 地址晚绑定虚函数
#include <iostream>
using namespace std;
//多态
//动物类
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat:public Animal
{
public:
//重写 函数返回值类型、函数名、参数列表都完全相同才叫重写
void speak() //子类virtual可写可不写,也可以写 virtual void speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog:public Animal
{
public:
virtual void speak()
{
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
//动态多态满足条件
//1、有继承关系
//2、子类重写父类的虚函数
//动态多态使用
//父类的引用或指针指向子类对象
void doSpeak(Animal &animal) // Animal & animal = cat
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
system("pause");
return 0;
}
/*
小猫在说话
小狗在说话
请按任意键继续. . .
*/
2. 多态的原理剖析
① 当没有发生重写时,子类中的虚函数表内部为父类的虚函数地址。
② 当子类重写父类的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址。
#include <iostream>
using namespace std;
//多态
//动物类
class Animal
{
public: //如果是虚函数,那么类里面存了一个指针,类占4个字节
virtual void speak() //如果是非静态成员函数void speak(),那么函数不在类上,空类占1个字节空间
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog:public Animal
{
public:
virtual void speak()
{
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
//动态多态满足条件
//1、有继承关系
//2、子类重写父类的虚函数
//动态多态使用
//父类的引用或指针指向子类对象
void doSpeak(Animal &animal) // Animal & animal = cat
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
void test02()
{
cout << "sizeof Animal = " << sizeof(Animal) << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
/*
sizeof Animal = 4
请按任意键继续. . .
*/
3. 案例1-计算器类
① 案例描述:分布利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类。
② 多态的优点:
- 代码组织结构清晰。
- 多可读性强。
- 利于前期和后期的扩展以及维护。
③ C++开发提倡利用多态设计程序架构,因为多态优点很多。
6.1 普通写法实现多态
#include <iostream>
using namespace std;
#include<string>
//普通方法实现多态
class Calculator
{
public:
int getResult(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if (oper == "-")
{
return m_Num1 - m_Num2;
}
else if (oper == "*")
{
return m_Num1 * m_Num2;
}
//如果想扩展新的功能,需要修改源码
//在真的开发中 提倡 开闭原则
//开闭原则:对扩展进行开发,对修改进行关闭
}
int m_Num1; //操作数1
int m_Num2; //操作数2
};
void test01()
{
//创建计算器对象
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
cout << c.m_Num1 << "-" << c.m_Num2 << "=" << c.getResult("-") << endl;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
10+10=20
10-10=0
10*10=100
请按任意键继续. . .
*/
6.2 多态设计程序架构
#include <iostream>
using namespace std;
#include<string>
//普通方法实现多态
#include <iostream>
using namespace std;
//多态
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器类
class AddCalculator : public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器类
class SubCalculator : public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器类
class MulCalculator : public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//多态使用条件
//父类指针或者引用指向子类对象
AbstractCalculator* abc = new AddCalculator; //这里用的是父类指针指向子类对象
abc->m_Num1 = 100;
abc->m_Num2 = 200;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完后记得销毁
delete abc;
//减法运算
abc = new SubCalculator; //指针并没有释放
abc->m_Num1 = 100;
abc->m_Num2 = 200;
cout << abc->m_Num1 << "-" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完后记得销毁
delete abc;
//乘法运算
abc = new MulCalculator; //指针并没有释放
abc->m_Num1 = 100;
abc->m_Num2 = 200;
cout << abc->m_Num1 << "*" << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完后记得销毁
delete abc;
}
int main()
{
test02();
system("pause");
return 0;
}
/*
100+200=300
100-200=-100
100*200=20000
请按任意键继续. . .
*/
4. 纯虚函数和抽象类
① 在多态中,通常父类中虚函数的实现时毫无意义的,主要都是调用子类重写的内容。因此,可以将虚函数改为纯虚函数。
② 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
③ 当类中有了纯虚函数,这个类也称为抽象类。
④ 抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
#include <iostream>
using namespace std;
//纯虚函数和抽象类
class Base
{
public:
//纯虚函数
//只要有一个纯虚函数,这个类称为抽象类
//抽象类特点:
//1、无法实例化对象
//2、抽象类的子类 必须要重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Son : public Base
{
public:
virtual void func()
{
cout << "func函数调用" << endl;
}
};
void test01()
{
//Base b; //抽象类是无法实例化对象
//new Base; //抽象类是无法实例化对象
//Son s; //子类中必须重写父类中的纯虚函数,否则无法实例化对象
Base* base = new Son;
base->func();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
func函数调用
请按任意键继续. . .
*/
5. 案例2-制作饮品
① 案例描述:制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料
② 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶。
#include <iostream>
using namespace std;
class AbstractDring
{
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee :public AbstractDring
{
public:
//煮水
virtual void Boil()
{
cout << "煮农夫山泉" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入糖和牛奶" << endl;
}
};
//制作茶叶
class Tea :public AbstractDring
{
public:
//煮水
virtual void Boil()
{
cout << "煮茶叶" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入枸杞" << endl;
}
};
//制作函数
void doWork(AbstractDring* abs)
{
abs->makeDrink();
delete abs; //释放
}
void test01()
{
//制作咖啡
doWork(new Coffee);
cout << "----------------" << endl;
doWork(new Tea);
}
int main()
{
test01();
system("pause");
return 0;
}
/*
煮农夫山泉
冲泡咖啡
倒入杯中
加入糖和牛奶
−−−−−−−−−−−−−−−−
煮茶叶
冲泡茶叶
倒入杯中
加入枸杞
请按任意键继续. . .
*/
6. 虚析构和纯虚析构
① 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
② 解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
③ 虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
④ 虚析构语法:virtual.类名(){}
⑤ 纯虚析构语法:
- virtual~类名 = 0;
- 类名::~类名(){}
① 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。
② 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。
③ 拥有纯虚析构函数的类也属于抽象类。
#include <iostream>
using namespace std;
#include<string>
//纯虚函数和纯虚机构
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
//纯虚函数
virtual void speak() = 0;
/*
//利用虚析构可以解决,父类指针释放子类对象时不干净的问题
virtual ~Animal()
{
cout << "Animal析构函数调用" << endl;
}
*/
//纯虚析构 需要声明也需要实现
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal纯虚析构函数调用" << endl;
}
class Cat : public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string* m_Name;
};
void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
//父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
Animal构造函数调用
Cat构造函数调用
Tom小猫在说话
Cat析构函数调用
Animal纯虚析构函数调用
请按任意键继续. . .
*/
7. 电脑组装
案例描述:电脑用主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储),将每个零件封装出抽象基类;并且提供不同的厂商生产不同的零件,例如intel厂商和Lennovo厂商;创建电脑类提供电脑工作的函数,并且调用每个零件工作的接口。测试时组装三台不同的电脑进行工作。
#include <iostream>
using namespace std;
#include<string>
//抽象不同零件类
//抽象CPU类
class CPU
{
public:
//抽象的计算函数
virtual void calculate() = 0;
};
//抽象显卡类
class VideoCard
{
public:
//抽象的显示函数
virtual void display() = 0;
};
//抽象内存条类
class Memory
{
public:
//抽象的存储函数
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU* cpu, VideoCard* vc, Memory* mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作的函数
void work()
{
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//提供析构函数 释放3个电脑零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private:
CPU* m_cpu; //CPU的零件指针
VideoCard* m_vc; //显卡零件指针
Memory* m_mem; //内存条零件指针
};
//具体厂商
//Intel厂商
class IntelCPU : public CPU
{
public:
virtual void calculate()
{
cout << "Intel的CPU开始计算了!" << endl;
}
};
class IntelVideoCard : public VideoCard
{
public:
virtual void display()
{
cout << "Intel的显卡开始计算了!" << endl;
}
};
class IntelMemory : public Memory
{
public:
virtual void storage()
{
cout << "Intel的内存条开始存储了!" << endl;
}
};
//Lenovo厂商
class LenovoCPU : public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};
class LenovoVideoCard : public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的显卡开始计算了!" << endl;
}
};
class LenovoMemory : public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的内存条开始存储了!" << endl;
}
};
void test01()
{
//第一台电脑零件
CPU* intelCpu = new IntelCPU;
VideoCard* intelCard = new IntelVideoCard;
Memory* intelMem = new IntelMemory;
cout << "第一台电脑开始工作:" << endl;
//创建第一台电脑
Computer* computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->work();
delete computer1;
cout << "---------" << endl;
cout << "第二台电脑开始工作:" << endl;
//创建第二台电脑
Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
computer2->work();
delete computer2;
cout << "---------" << endl;
cout << "第三台电脑开始工作:" << endl;
//创建第三台电脑
Computer* computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemory);
computer3->work();
delete computer3;
}
int main()
{
test01();
system("pause");
return 0;
}
/*
第一台电脑开始工作:
Intel的CPU开始计算了!
Intel的显卡开始计算了!
Intel的内存条开始存储了!
−−−−−−−−−
第二台电脑开始工作:
Lenovo的CPU开始计算了!
Lenovo的显卡开始计算了!
Lenovo的内存条开始存储了!
−−−−−−−−−
第三台电脑开始工作:
Intel的CPU开始计算了!
Lenovo的显卡开始计算了!
Lenovo的内存条开始存储了!
请按任意键继续. . .
*/
14.C++文件操作
1. 文件操作简介
① 程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。
② 通过文件可以将数据持久化。
③ C++中对文件操作需要包含头文件
④ 文件类型分为两种:
- 文本文件:文件以文本的ASCII码形式存储在计算机中。
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们。
⑤ 操作文件的三大类
- ofstream:写操作
- ifstream:读操作
- fstream:读写操作
⑥ 文件打开方式可以配合使用,利用|操作符。
- 例如,用二进制方式写文件 ios:: binary | ios:: out
14.1 文本文件--写文件
① 写文件步骤如下:
- 包含头文件 #include <fstream>
- 创建流对象 ofstream ofs;
- 打开文件 ofs.open("文件路径",打开方式);
- 写内容 ofs<<"写入数据";
- 关闭文件 ofs.close();
#include <iostream>
using namespace std;
#include<fstream> //1、包含头文件 fstream
//文本文件 写文件
void test01()
{
//2、创建流对象
ofstream ofs;
//3、指定打开方式
ofs.open("test.txt", ios::out); //当没有指定某盘路径时,创建的文件在该项目文件的路径下
//4、写内容
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
//5、关闭文件
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果:
14.2 文本文件--读操作
① 读文件与写文件步骤相似,但是读取方式相对比较多。
② 读文件步骤如下:
- 包含头文件 #include<fstream>
- 创建流对象 ifstream ifs;
- 打开文件并判断文件是否成功打开 ifs.open("文件路径",打开方式);
- 四种方式读取
- 关闭文件 ifs.close();
#include <iostream>
using namespace std;
#include<fstream>//1、包含头文件 fstream
#include<string>
//文本文件 读文件
void test01()
{
//2、创建流对象
ifstream ifs;
//3、打开文件,并且判断是否打开成功
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//4、读数据
/*
//第一种
char buf[1024] = { 0 }; //字符数组初始化全为0
while (ifs >> buf) //一行一行数据读,存到buf里,当读不到数据的时候,返回False,退出循环
{
cout << buf << endl;
}
*/
/*
//第二种
char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf))) //第一个参数为数据放到哪里(地址),第二个参数为最多要读多少个字节数
{
cout << buf << endl;
}
*/
/*
//第三种
string buf;
while (getline(ifs,buf))
{
cout << buf << endl;
}
*/
//第四种
char c;
while ((c = ifs.get()) != EOF) //每一次读取一个字符,字符放入c
{ //EOF表示读取到文件尾
cout << c;
}
//5、关闭文件
ifs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
姓名:张三
性别:男
年龄:18
请按任意键继续. . .
*/
14.3 二进制文件--写文件
① 以二进制的方式对文件进行读、写操作,打开方式要指定为 ios::binary
② 二进制方式写文件主要利用流对象调用成员函数write,函数原型:ostream write(const char * buffer,int len)
③ 参数解释:字符指针buffer指向内存中一段内存空间,len是读写的字节数。
#include <iostream>
using namespace std;
#include<fstream>//1、包含头文件
//二进制文件 写文件
class Person
{
public:
char m_Name[64]; //姓名
int m_Age; //年龄
//写字符串的时候最好不要用C++的string,有可能出现问题,最好用C的数组写字符串,因为它底层是用C写的
};
void test01()
{
//2、创建流文件
ofstream ofs;
//3、打开文件
ofs.open("person.txt", ios::out | ios::binary); //二进制的方式写文件
//ofstream ofs.open("person.txt",ios::out | ios::binary) 也可以两步合成一步
//4、写文件
Person p = { "张三",18 };
ofs.write((const char*)&p, sizeof(Person)); //对p取地址,然后强转为常量指针
//5、关闭文件
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果:
14.4 二进制文件--读文件
① 二进制方式读文件主要利用流对象调用成员函数read。
② 函数原型:istream& read(char *buffer,int len);
③ 参数解释:字符串buffer指向内存中一段内存空间,len是读写的字节数。
#include <iostream>
using namespace std;
#include<fstream>//1、包含头文件
//二进制文件 读文件
class Person
{
public:
char m_Name[64]; //姓名
int m_Age; //年龄
//写字符串的时候最好不要用C++的string,有可能出现问题,最好用C的数组写字符串,因为它底层是用C写的
};
void test01()
{
//2、创建流对象
ifstream ifs;
//3、打开文件
ifs.open("person.txt", ios::in | ios::binary); //二进制的方式读文件
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//4、读文件
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Name << endl;
//5、关闭文件
ifs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
/*
姓名:张三 年龄:张三
请按任意键继续. . .
*/
2. 导入其他文件的类
2.1 point.h头文件
In [ ]:
//这是point.h头文件 #pragma once //防止头文件重复包含,防止头文件冲突 #include <iostream> //标准输入输出 using namespace std; //标准命名空间 //只要函数声明、变量声明 //成员函数只需要声明就好了,末尾有分号。 class Point { public: //设置x void setX(int x); //获取x int getX(); //设置y void setY(int y); //获取y int getY(); private: int m_X; int m_Y; };
2.2 point.cpp源文件
In [ ]:
//这是point.cpp源文件
#include "point.h"
//定义函数时,要加上作用域,"双冒号::"表示Point作用域下的函数
void Point::setX(int x)
{
m_X = x;
}
//获取x
int Point::getX()
{
return m_X;
}
//设置y
void Point::setY(int y)
{
m_Y = y;
}
//获取y
int Point::getY()
{
return m_Y;
}
2.3 circle.h头文件
In [ ]:
#pragma once
#include <iostream> //标准输出流
using namespace std; //标准命名空间
#include "Point.h" //一个类中用到另一个类,把另一个类包含的头文件包含进来
//圆类
class Circle
{
public:
//设置半径
void setR(int r);
//获取半径
int getR();
//设置圆心
void setCenter(Point center);
//获取圆心
Point getCenter();
private:
int m_R; //半径
Point m_Center; //圆心 //在类中可以让另一个类 作为本类中的成员
};
2.4 circle.cpp头文件
In [ ]:
//这是circle.cpp头文件
#include "circle.h"
//设置半径
void Circle::setR(int r)
{
m_R = r;
}
//获取半径
int Circle::getR()
{
return m_R;
}
//设置圆心
void Circle::setCenter(Point center)
{
m_Center = center;
}
//获取圆心
Point Circle::getCenter()
{
return m_Center;
}
2.5 main.cpp文件
In [ ]:
//这个是主文件 .cpp文件
#include <iostream>
using namespace std;
#include "circle.h" //想用点类,就要包含点类的头文件
#include "point.h" //想用圆类,就要包含点类的头文件
//判断点和圆关系
void isInCircle(Circle& c, Point& p)
{
//计算两点之间距离 平方
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY()); //c.getCenter()返回的是圆心点center类
//可以调用圆心点center类中的方法
//计算半径的平方
int rDistance = c.getR() * c.getR();
//判断关系
if (distance == rDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
int main()
{
//创建圆
Circle c;
c.setR(10);
Point center;
center.setX(10); //设置点的横坐标
center.setY(0); //设置点的纵坐标
c.setCenter(center); //设置点类传入圆类
//创建点
Point p;
p.setX(10);
p.setY(10);
//判断关系
isInCircle(c, p);
system("pause");
return 0;
}
标签:std,cout,int,学习心得,C++,网课,using,include,函数 From: https://blog.csdn.net/2404_82622750/article/details/143622703