初识C++
C++标准格式
#include <iostream>
using namespace std;
int main()
{
cout << "hello world!" << endl;
system("pause");
return 0;
}
变量
作用:给一段指定的内存空间起名,方便操作这段内存。
语法:数据类型 变量名 = 初始值;
#include <iostream>
using namespace std;
int main()
{
int a = 10;
cout << "a的值为:" << a << endl;
system("pause");
return 0;
}
常量
作用:用于记录程序中不可更改的数据
C++定义常量的两种方式
- #define 宏常量 : #define 常量名 常量值
- 通常在文件上方定义,表示一个常量
- const修饰的变量:const 数据类型 常量名 = 常量值
- 通常在变量定义前面加关字const,修饰该变量为常量,不可修改
#define day 7
int main ()
{
cout << "一周共有多少天" << day << endl;
const int month = 12;
cout << "一年总共有多少个月份" << month << endl;
}
关键字
作用:关键字是C++中预先保留的单词(标识符)</br>
在定义变量或者常量的时候,不要用关键字
if | else | while | signed | throw | union | this |
---|---|---|---|---|---|---|
int | char | double | unsigned | const | goto | virtual |
for | char | brack | auto | class | operator | case |
do | long | typedef | static | friend | template | default |
new | void | register | extern | return | enum | inline |
try | short | continue | sizeof | switch | pricate | protected |
asm | while | catch | delete | public | volatile | struct |
标识符命名规则
作用:C++规定给标识符(变量 常量)命名时,有一套自己的规则
- 标识符不用是关键字
- 标识符只能由字母、数字、下划线组成
- 第一个字符必须为字母或者下划线
- 标识符中字母区分大小写
数据类型
C++规定在创建一个变量或者常量时,必须要指定出相应的数据类型,否则无法给变量分配内存
1.整型
作用:整型变量标识的数是整型的数据</br>
C++中能够标识整型的类型有一下几种方式,区别在于所占内存空间不同;
数据类型 | 占用空间 | 取值范围 |
---|---|---|
short(短整型) | 2字节 | (-215~215-1) |
int(整型) | 4字节 | (-231~231-1) |
long(长整型) | Windows为4字节,linux为4字节(32位),8字节(64位) | (-231~231-1) |
long long(长长整型) | 8字节 | (-263~263-1) |
2.sizeof关键字
作用:利用sizeof关键字可以统计数据类型所占内存大小
语法:sizeof(数据类型 / 变量)
3.实型(浮点型)
作用:用于表示小数</br>
浮点型变量分为两种:
- 单精度float
- 双精度double
两者的区别在于标识的有效数字范围不同</br>
默认情况下 输出一个小数会显示出6位有效数据</br>
数据类型 | 占用空间 | 有效字符范围 |
---|---|---|
float | 4字节 | 7位有效数字 |
double | 8字节 | 15~16位有效数字 |
int main ()
{
float f1 = 2.14f;
double d1 = 3.14;
cout << "float sizeof = " << sizeof(f1) << endl;
cout << "double sizeof = " << sizeof(d1) << endl;
//科学计数法
float f2 = 3e2 ; //3 * 10^2
cout << "f2 = " << f2 << endl;
folat f3 = 3e-2; //3 * 0.1^2
cout << "f3 = " << f3 << endl;
}
4.字符型
作用:字符型变量用于显示单个字符</br>
语法:char ch = 'a';</br>
注意1:在显示支付变量时,用单引号将字符括起来,不要用双引号</br>
注意2:单引号只能有一个字符,不可以是字符串</br>
- c和c++中字符型变量只占用一个字节
- 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放到存储单元
int main()
{
char ch = 'a';
cout << ch << endl;
cout << sizeof(char) << endl;
cout << (int)ch << endl; //查看字符a对应的ACSII值
ch = 97; //可以直接用ACSII给字符型变量赋值
cout << ch << endl;
}
5.转义字符
作用:用于表示一些不能显示出来的ASCII字符</br>
现阶段我们常用的转义字符有:\n \\ \t
6.字符串型
作用:用于表示一串字符
- C风格字符串:char 变量名[]="字符串值"
int main()
{
char str1[] = "hello world";
cout << str1 << endl;
}
- C++风格字符串:string 变量名 = "字符串值"
int main()
{
string str2 = "hello world";
cout << str2 << endl;
}
7.布尔数据类型
作用:布尔数据类型代表真或假的值</br>
bool类型只有两个值
- true --- 真(本质是1)
- false --- 假(本质是0)
bool类型占1个字节大小
int main()
{
bool flag = true;
cout << flag << endl; //1
flag = false;
cout << flag << endl;//0
cout << "sizeof bool = " << sizeof(bool) << endl;
}
8.数据的输入
作用:用于从键盘获取数据
关键字:cin
语法:cin >> 变量;
运算符
作用:用于执行代码的运算
运输算符 | 作用 |
---|---|
算数运算符 | 用于处理四则运算 |
赋值运算符 | 用于将表达式的值赋给变量 |
比较运算符 | 用于表达式的比较,并返回一个真值或者假值 |
逻辑运算符 | 用于根据表达式的值返回真值或假值 |
1.算术运算符
作用:用于处理四则运算
算数运算符包括以下符号:
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | -3 | -3 |
+ | 加 | 10+5 | 15 |
- | 减 | 10-5 | 5 |
* | 乘 | 10*5 | 50 |
/ | 除 | 10/5 | 2 |
% | 取模(求余) | 10%3 | 1 |
++ | 前置递增 | a=2;b=++a; | a=3;b=3; |
++ | 后置递增 | a=2;b=a++; | a=3;b=2; |
-- | 前置递减 | a=2;b=--a; | a=1;b=1; |
-- | 后置递减 | a=2;b=a--; | a=1;b=2; |
2.赋值运算符
作用:用于将表达式的值赋给变量
赋值运算符包括一下几个符号:
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
= | 赋值 | a=2;a=3; | a=2;b=3; |
+= | 加等于 | a=0;a+=2 | a=2; |
-= | 减等于 | a=5;a-=3 | a=2; |
*= | 乘等于 | a=2;a*=2; | a=4; |
/= | 除等于 | a=4;a/=2; | a=2; |
%= | 模等于 | a=3;a%2; | a=1; |
3.比较运算符
作用:用于表达式的比较,并返回一个真值或假值
比较运算符有以下符号:
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
== | 相等于 | 4 == 3 | 0 |
!= | 不等于 | 4!=3 | 1 |
< | 小于 | 4<3 | 0 |
> | 大于 | 4>3 | 1 |
<= | 小于等于 | 4<=3 | 0 |
>= | 大于等于 | 4>=1 | 1 |
4.逻辑运算符
作用:用于根据表达式的值返回真值或假值
逻辑运算符有以下符号:
运算符 | 术语 | 示例 | 结果 |
---|---|---|---|
! | 非 | !a | 如果a为假,则!a为真;如果a为真,则!a为假。(去反) |
&& | 与 | a && b | 如果a和b都为真,则结果为真,否则为假。 |
或 | a || b | 如果a和b有一个为真,则结果为真,二者都有假时,结果为假。 |
程序流程结构
c/c++支持最基本的三种程序运算结构:顺序结构、选择结构、循环结构
- 顺序结构:程序按照顺序执行,不发生跳转
- 选择结构:依据条件是否满足,有选择的执行相应功能
- 循环结构:依据条件是否满足,循环多次执行某段代码
1.选择结构
1.1 if语句
作用:执行满足条件的语句
if语句的三种形式
- 单行格式if语句
- 多行格式if语句
- 多条件的if语句
嵌套if语句:在if语句中,可以嵌套使用if语句,达到更精确地条件判断
案例需求:
- 提升用户输入一个高考分数,根据分数做如下判断
- 分数如果大于600分视为考上一本大学,大于500分考上二本,大于400考上三本,其余视为未考上本科;
- 在一本分数中,如果大于700分,考入北大,大于650分,考入清华,大于600考入人大
int main()
{
int source = 0;
cout << "请输入您的考上分数" << endl;
cin >> source;
cout << "你输入的分数为" << source << endl;
if (source > 600)
{
cout << "恭喜你考上了一本大学" << endl;
if (source > 700)
{
cout << "恭喜你考上了北大" << endl;
}
else if (source > 650)
{
cout << "恭喜你考上了清华" << endl;
}
else {
cout << "恭喜你考上了人大" << endl;
}
}
else if (source > 500)
{
cout << "恭喜你考上了二本大学" << endl;
}
else if (source > 400)
{
cout << "恭喜你考上了三本大学" << endl;
}
else {
cout << "你未考上本科" << endl;
}
}
1.2三目运算符
作用:通过三目运算符实现简单的判断
语法:表达式1 ?表达式2 :表达式3
解释:
- 如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
- 如果表达式1的值为假,执行表达式3,并返回表达式3的结构;
示例:
int main()
{
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;
}
1.3 switch语句
作用:执行多条件分支语句
语法:
switch(表达式)
{
case 结果1 : 执行语句;
break;
case 结果2 : 执行语句;
break;
case 结果3 : 执行语句;
break;
......
default : 执行语句;
break;
}
2.循环语句
2.1 while循环语句
作用:满足循环条件,执行循环语句
语法:while(循环条件){ 循环语句 }
解释:只要循环语句的结果为真,就执行循环语句
需求:打印0-9之间的数
int main()
{
int number = 0;
while (number < 10)
{
cout << number << endl;
number++;
}
}
案例:0-100生成随机数,
#include <iostream>
#include <ctime>
using namespace std;
int main()
{
srand((unsigned int)time(NULL)); //添加随机种子,作用是利用系统时间生成随机数,防止每次随机数都一样;
int num = rand() % 100 + 1; //生成一个随机数
int val = 0;
while(1)
{
cin << val;
if ( val > num)
{
cout >> "您输入的数过大了" >> endl;
}
else if (val < num)
{
cout >> "您输入的数过小了" >> endl;
}
else
{
cout >> "恭喜您猜对了,你太厉害了" >> endl;
brank;
}
}
return 0;
}
2.2 do...while循环语句
作用:满足循环条件,执行循环语句
语法:do { 循环语句 } while(循环条件);
注意:与while的区别在于do...while会先执行一次循环语句,再判断循环条件
示例:
int main()
{
int num = 0;
do
{
cout << num << endl;
num++;
}while(num < 10);
system("pause");
return 0;
}
案例:输出三位的水仙花数
int main()
{
int num = 100;
do
{
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);
}
2.3 for循环语句
作用:满足循环条件,执行循环语句
语句:for ( 起始表达式;条件表达式;末尾表达式) { 循环语句; }
int main()
{
for (int i = 0 ; i < 10 ;i++)
{
cout << i << endl;
}
system("pause");
return 0;
}
案例:遇到个位或者十位有7的都输入敲桌子
int main()
{
for (int num = 0; i < 100 ;i++)
{
if (num % 7 == 0 || num % 10 == 7 || num / 10 == 7)
{
cout << "敲桌子" << endl;
}
else
{
cout << i << endl;
}
}
}
2.4 嵌套循环
作用:在循环体中再嵌套一层循环,解决一些实际问题
例如:在屏幕中打印10*10的星号
int main()
{
int i,j;
for (i = 0;i<10 ;i++)
{
for (j = 0;j<10;j++)
{
cout << "* " << endl;
}
}
return 0;
}
乘法口诀表
int main()
{
int i , j ;
for (i = 1 ;i < 10 ;i++)
{
for (j = 1 ; j< i+1;j++)
{
cout << i << "x" << j << "=" << i*j << " ";
}
cout << endl;
}
return 0;
}
3.跳转语句
3.1 break语句
作用:用于跳出选择结构或者循环结构
break使用的时机:
- 出现在switch条件语句中,作用是终止case并跳出switch
- 出现在循环语句中,作用是跳出当前的循环语句
- 出现在嵌套循环中,跳出最近的内层循环语句
3.2 continue语句
作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环
示例
int main()
{
for (int i;i < 100;i++)
{
if(i % 2 == 0)
{
cotinue;
}
cout << i << endl;
}
}
3.3 goto语句
作用:可以无条件跳转语句
语法: goto 标记;
解释:如果标记的名称存在,执行到goto语句中,会跳转到标记的位置;
int main()
{
cout << "1" << endl;
goto FLAG;
cout << "1" << endl;
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
FLAG;
cout << "5" << endl;
}
4.一维数组
4.1一维数组定义方式
一维数组定义的三种方式:
- 数据类型 数组名[数组长度];
- 数据类型 数组名[数组长度] = {值1,值2,值3};
- 数据类型 数组名[] = {值1,值2 ...};
int mian ()
{
//方法1
int arr[5];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
arr[3] = 40;
arr[4] = 50;
//方法2
int arr2[5] = {10,20,30,40,50};
//方法3
int arr3[] = {10,20,30,40,50,60,70,80,90};
cout << arr3[0] << endl;
cout << arr3[1] << endl;
cout << arr3[2] << endl;
cout << arr3[3] << endl;
cout << arr3[4] << endl;
for (int i = 0;i <10 ;i++)
{
cout << arr3[i] << endl;
}
}
总结1: 数组名的命名规则与变量名规则一致,不能和变量重名
总结2: 数组中下标是从0开始索引
4.2 一维数组数组名
一维数组的用途:
- 可以统计整个数组在内存中的长度
- 可以获取数组在内存中的首地址
int main ()
{
//数组名的用途
//1.可以通过数组名统计整个数组占用内存大小
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
cout << "整个数组占用内存空间为:" << sizeof(arr) <<endl;
cont << "每个元素在数组中占用内存空间大小为:" << sizeof(arr[0]) << endl;
cout << "数组中元素个数为:" << sizeof(arr) / sizeof(arr[0]) << endl;
//2.可以通过数组名查看数组首地址
cout << "数组的首地址为:" << (int)arr << endl;
cout << "数组的首地址为:" << (int)&arr[0] << endl;
//数组名是常量,不能做赋值操作
}
案例:五只小猪称体重
案例描述:
在一个数组中记录了五只小猪的体重,如:int arr[5] = {300,350,200,400,250}; 找出并打印最重的小猪体重。
int main()
{
int max;
int arr[5] = {300,350,200,400,250};
max = arr[0];
for(int i = 0; i <5 ;i++)
{
if (arr[i] > max)
{
max = arr[i];
}
}
cout << "五只小猪中最重的是:" << max << endl;
}
案例2:数组元素逆置
案例描述:请声明一个5个元素的数组,并且将元素逆置
(如原数组元素为:1,3,2,5,4 逆置后输出结果为:4,5,2,3,1)
int test()
{
int arr[] = { 1,3,2,5,4 };
cout << "逆置前的数组为:"<< endl;
for (int i = 0; i <5 ;i++)
{
cout << arr[i] << endl;
}
//实现逆置
//1.记录起始下标位置
//2.记录结束下标位置
//3.起始下标与结束下标的元素互换
//4.起始位置加1,结束位置减1
//5.循环执行1操作,直到起始位置>=结束位置
int start = 0 ; //设置起始下标
int end = sizeof(arr) / sizeof(arr[0]) - 1; //设置结束下标
while(start < end)
{
int temp;
temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start ++;
end --;
}
cout << "逆置后的数组为:"<< endl;
for (int i = 0; i <5 ;i++)
{
cout << arr[i] << endl;
}
}
int main ()
{
test();
return 0;
}
冒泡排序
作用:最常用的排序算法,对数组内元素进行排序
- 比较相邻的元素,如果第一个比第二个大,就交换他们
- 对每一对相邻元素做同样的工作,执行完成后,找到第一个最大值。
- 重复以上的步骤,每次比较次数-1,直到不需要比较
- 算法逻辑:两两交换,前面一个数比后面一个数大就交换,否则不交换,继续便利下两个数
排序总轮数 = 元素个数 - 1;
每轮对比次数 = 元素个数 - 排序轮数 - 1;
int main()
{
int arr[9] = {2,4,0,5,7,1,3,8,9};
cout << "排序前为:" << endl;
for (int i = 0; i<9 i++)
{
cout << arr[i] << " ";
}
cout << endl;
for (int i = 0;i < 9-1 ;i++)
{
for (int j = 0;j<9-i-1;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;
}
5.二维数组
二位数组就是在一维数组上,多加一个维度
5.1 二维数组的定义方式
二位数组定义的四种方式:
- 数据类型 数组名[行数][列数];
- 数据类型 数组名[行数][列数] = {{ 数据1,数据2},{数据3,数据4}};
- 数据类型 数组名[行数][列数] = {数据1,数据2,数据3,数据4};
- 数据类型 数组名[][列数] = {数据1,数据2,数据3,数据4};
- 建议:以上4钟定义方式,利用第二种更加直观,提高代码的可读性
5.2 二维数组数组名
- 查看二维数组所占内存空间
- 获取二维数组首地址
示例
int main()
{
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 << "二维数组的首地址:" << arr << endl;
}
5.3 二维数组应用案例
考试成绩统计:
案例描述:有三名同学(张三,李四,王五),在一次考试中的成绩分别如下表,请分别输出三名同学的总成绩
语文 | 数学 | 英语 | |
---|---|---|---|
张三 | 100 | 100 | 100 |
李四 | 90 | 50 | 100 |
王五 | 60 | 70 | 80 |
int main()
{
int sources[3][3] =
{
{100,100,100},
{90,50,100},
{60,70,80}
};
for(int i ;i <3;i++)
{
int sum;
for(int j;j<3;j++)
{
sum += source[i][j];
cout<< source[i][j] <" ";
}
cout << "第"<<i+1<<"的总分为:" << sum << endl;
}
}
6 函数
6.1 概述
作用:将一段经常使用的代码封装起来,减少重复代码。
一个较大的程序,一般分为若干个程序模块,每个模块实现特定的功能。
6.2 函数的定义
函数的定义一般主要有5个步骤:
- 返回值类型
- 函数名
- 参数列表
- 函数体语句
- return表达式
语法:
返回值类型 函数名 (参数列表)
{
函数体语句
return 表达式;
}
6.3 函数的调用
功能:使用定义好的函数
语法:函数名(参数)
示例:
int add(int num1,int num2)
{
int sum = sum1 + sum2;
return sum;
}
int main()
{
int a = 10;
int b = 20;
int sum = add(a,b);
cout << "sum = " << sum << endl;
}
总结:函数定义里小括号称为形参,函数调用时传入的参数称为实参。
6.4 值传递
- 所谓值传递,就是函数调用时实参将数值传入给形参
- 值传递时,如果形参发生,并不会影响实参
6.5 函数的常见样式
常见的函数样式有4种
- 无参无返
- 有参无返
- 无参有返
- 有参有返
6.6 函数的声明
作用:告诉编译器函数名称及如何调用函数,函数的实际主体可以单独定义
- 函数的声明可以多次,但是函数的定义只能由一次。
6.7 函数的分文件编写
作用:让代码结构更加清晰
函数分文件编写一般有4个步骤
- 创建后缀名为.h的头文件
- 创建后缀为.cpp的源文件
- 在头文件中写函数的声明
- 在源文件中写函数的定义
7 指针
7.1 指针的基本概念
指针的作用:可以通过指针间接访问内存
- 内存编号是从0开始记录的,一般用十六进制数字表示
- 可以利用指针变量保存地址
7.2 指针变量的定义和使用
指针变量定义语法: 数据类型 *变量名;
示例:
int main()
{
//1. 指针的定义
int a = 10;
//指针定义语法:数据类型 *变量名;
int *p;
//指针变量赋值
p = &a; //指针指向变量a的地址;
cout << &a <<endl;
cout << p << endl;
//2. 指针的使用
//通过*操作指针变量指向的内存
cout << "p = " << *p << endl;
}
7.3 指针所占内存空间
在32位操作系统下,指针变量占用4个字节空间。64位下占8个字节。
提问:指正也是数据类型,那么这种数据类型占用多少内存空间?
示例:
int main ()
{
int a = 10;
int *p;
p = &a ;
cout << *p <<end; //*解引用
cout << sizeof(p) << endl;
cout << sizeof(char *) << endl;
cout << sizeof(float *) << endl;
cout << sizeof(double *) << endl;
}
7.4 空指针和野指针
空指针:指针变量执行内存中编号为0的空间。
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
示例1:空指针
int main()
{
//指针变量p指向内存地址编号为0的空间
int *p = NULL;
//访问空指针报错
//内存编号0 ~ 255为系统占用内存,不允许用户访问
cout << *p << endl;
}
野指针:指针变量指向非法的内存空间
示例2:野指针
int main ()
{
//指针变量p执行内存地址编号为0x1100的空间;
int *p = (int *)0x1100;
//访问野指针报错
cout << *p << endl;
}
7.5 const修饰指针
const修饰指针有三种情况
- const修饰指针 ---常量指针
- const int *p = &a
- 特点:指针的指向可以修改,但是指针指向的值不可以修改
- const修饰常量 ---指针常量
- int * const p = &a
- 特点:指针的执行不可以修改,但是指针指向的值可以修改
- const即修饰指针,有修饰常量
- const int * const p = &a
- 特点:指针的指向和指针的值都不可以修改
7.6 指针和数组
作用:利用指针访问数组中元素
示例:
int main ()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
cout << "第一个元素:" << arr[0] << endl;
cout << "指针访问第一个元素:" << *p << endl;
for (int i = 0;i <10;i++)
{
cout << *p << endl;
p++;
}
}
7.7 指针和函数
作用:利用指针作函数参数,可以修改实参的值
示例:
int swap01(int a ,int b )
{
int temp;
temp = a;
b = a;
a = temp;
}
int swap02(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a =10;
int b =20;
swap01(a,b);
swap01(&a,&b);
}
7.8 指针、数组、函数
案例描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排序
例如数组:int arr[10] = {4,3,6,9,1,2,10,8,7,5};
void add (int *arr ,int len)
{
for(int i=0;i<len-1;i++)
{
for(int j=0;j<len-i-1;j++)
{
if(arr[j] > arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] =temp;
}
}
}
}
void pro(int *arr,int len)
{
for(int i=0;i<len;i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
int main()
{
int arr[10] = {4,3,6,9,1,2,10,8,7,5};
int lan = sizeof(arr) / sizeof(arr[0]);
add(arr,len);
pro(arr,len);
}
8 结构体
8.1 结构体基本概念
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
8.2 结构体定义和使用
语法: struct 结构体名 { 结构体成员列表 };
通过结构体创建变量的方式有三种:
- struct 结构体 变量名
- struct 结构体 变量名 = { 成员1值 ,成员2值 ...}
- 定义结构体时顺便创建变量
示例:
//结构体定义
struct student
{
string name;
int age;
int score;
}s3;
int main()
{
//赋值方式
//1.直接赋值
struct student s1;
s1.name = "张三";
s1.age = 18;
s1.score = 100;
cout << "姓名: " << s1.name << "年龄: " << s1.age << "分数: " << s1.score << endl;
//2. struct 结构体 变量名 = { 成员1值 ,成员2值 ...}
struct student s2 = { "李四",19,80 };
cout << "姓名: " << s2.name << "年龄: " << s2.age << "分数: " << s2.score << endl;
//3.定义结构体时顺便创建变量
s3.name = "王五";
s3.age = 20;
s3.score = 90;
cout << "姓名: " << s3.name << "年龄: " << s3.age << "分数: " << s3.score << endl;
}
- 总结1:定义结构体时的关键字是struct,不可省略
- 总结2:创建结构体变量时,关键字struct可以省略
- 总结3:结构体变量利用操作符"."访问成员
8.3 结构体数组
作用:将自定义的结构体放入到数组中方便维护
语法:struct 结构体名 数组名[元素个数] = { {},{}, ...{} };
示例:
struct Student
{
string name;
int age;
int score;
};
int main ()
{
struct Student Arr[3] =
{
{"张三",18,90},
{"李四",20,80},
{"王五",25,90}
};
Arr[2].name = "赵六";
Arr[2].age = 50;
Arr[2].score = 60;
for(int i;i<3;++)
{
cout << "姓名:" << Arr[i].name << "年龄:" << Arr[i].age << "成绩:" << Arr[i].score << endl;
}
}
8.4 结构体指针
作用:通过指针访问结构体中的成员
- 利用操作符 -> 可以通过结构体指针访问结构体属性
示例:
struct Student
{
string name;
int age;
int score;
};
int main()
{
Student s = {"张三",18,90};
Student *p = &s;
cout << "姓名:" << p->name << "年龄:" << p->age << "成绩:" << p->score << endl;
}
- 结构体可以使用箭头来访问属性。
8.5 结构体嵌套结构体
作用:结构体中的成员可以是另一个结构体
例如:每个老师辅导一个学员,一个老师的结构体重,记录一个学生的结构体
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 = 1000;
t.name = "老王";
t.age = 60;
t.stu.name = "小王";
t.stu.age = 18;
t.stu.score = 100;
//输出他们...
}
8.6 结构体做函数参数
作用:将结构体作为参数向函数中传递
传递方式有两种
- 值传递
- 地址传递
示例:
struct Student
{
string name;
int age;
int score;
};
//值传递
void printStudent1(struct Student s)
{
s.age = 100;
cout << "值传递函数中打印 姓名:" << s.name << "年龄:" << s.age << "成绩:" << s.score << endl;
}
//地址传递
void printStudent2(struct Student *p)
{
p->age = 200;
cout << "地址传递函数中打印 姓名:" << p->name << "年龄:" << p->age << "成绩:" << p->score << endl;
}
int main()
{
struct Student s;
s.name = "张三";
s.age = 19;
s.score = 85;
printStudent(s);
printStudent(&s);
cout << "main函数中打印 姓名:" << s.name << "年龄:" << s.age << "成绩:" << s.score << endl;
}
- 总结:如果不想修改主函数重的数据,用值传递,反之用地址传递
8.7 结构体重const使用场景
作用:用const来防止误操作
示例:
struct student
{
string name;
int age;
int score;
}
void printStudent (const student *stu )
{
//stu->age = 100; //操作失败,因为加了const修饰
cout << "姓名:" << p->name << "年龄:" << p->age << "成绩:" << p->score << endl;
}
int main()
{
student stu = {"张三",18,90};
printStudent(&stu);
return 0;
}
8.8 结构体案例
8.81 案例一
案例描述:
学校找找做毕业设计项目,每个老师带领5个学生,总共又3名老师,需求如下
设计学生和老师的结构体,其中在老师的结构体中,有老师姓名和一个存放5名学生的数组作为成员
学生的成员有姓名、考试分数,常见数组存放3名老师,通过函数给每个老师及所带的学生赋值
最综打印出老师数据以及老师所带的学生数据。
实例:
#include <iostream>
using namespace std;
#include <string>
#include <ctime>
struct Student
{
string name;
int score;
};
struct Teacher
{
string name;
struct Student Arr[5];
};
void cindata(struct Teacher tArr[], int len)
{
string arr = "abcde";
for(int i=0 ;i<len;i++)
{
tArr[i].name = "Teacher_";
tArr[i].name += arr[i];
for(int j =0;j<5;j++)
{
tArr[i].Arr[j].name = "Student_";
tArr[i].Arr[j].name += arr[j];
int ren = rand() % 61 + 40;
tArr[i].Arr[j].score = ren;
}
}
}
void prodata(struct Teacher tArr[], int len)
{
for(int i =0;i<len;i++)
{
cout << "老师姓名:" << tArr[i].name << endl;
for (int j = 0;j<5;j++)
{
cout << "\t学生姓名:" << tArr[i].Arr[j].name << " " <<
"学生成绩:" << tArr[i].Arr[j].score << endl;
}
}
}
int main()
{
//随机数种子,根据系统时间来取随机数
srand((unsigned int)time(NULL));
//定义结构体三个老师
struct Teacher tArr[3];
int len = sizeof(tArr) / sizeof(tArr[0]);
cindata(tArr,len);
prodata(tArr,len);
return 0;
}
8.8.2 案例2
案例描述:
设计一个英雄的结构体,包括成员姓名,年龄,性别;
创建结构体数组,数组中存放5名英雄。
通过冒泡排序的算法,将数组中的英雄按照年龄进行升序排序,最终打印排序后的结果。
#include <iostream>
using namespace std;
#include <string>
struct Superman
{
string name;
int age;
string sex;
};
void BubbleSort(struct Superman arr[],int len)
{
for (int i = 0;i<len-1;i++)
{
for(int j = 0;j<len-i-1;j++)
{
if(arr[j].age > arr[j+1].age)
{
struct Superman temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
void printsort(struct Superman arr[],int len)
{
for(int i=0;i<len;i++)
{
cout << "姓名:" << arr[i].name << " " << "年龄:" << arr[i].age << " " << "性别:" << arr[i].sex <<endl;
}
}
int main()
{
struct Superman arr[5] =
{
{"刘备",23,"男"},
{"关羽",22,"男"},
{"张飞",20,"男"},
{"赵云",21,"男"},
{"貂蝉",19,"女"}
};
int len = sizeof(arr) / sizeof(arr[0]);
cout << "排序前:" << endl;
printsort(arr,len);
cout << "排序后:" << endl;
BubbleSort(arr,len);
printsort(arr,len);
return 0;
}
通讯录管理系统
1.系统需求
通讯录是一个可以记录亲人、好友信息的工具。
本教程主要利用C++来实现一个通讯录管理系统
系统中需要实现的功能如下:
- 添加联系人: 向通讯录中添加新人,信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人
- 显示联系人: 显示通讯录中所有联系人信息
- 删除联系人: 按照姓名进行删除指定联系人
- 查找联系人: 按照姓名查看指定联系人信息
- 修改联系人: 按照姓名修改指定联系人
- 清空联系人: 清空通讯录中所有信息
- 退出通讯录: 推出当前使用的通讯录
2.添加联系人
功能描述:
实现添加联系人功能,联系人上限为1000人,联系人信息包括(姓名、性别、年龄、联系电话、家庭住址)
添加联系人实现步骤:
- 设计联系人结构体
- 设计通讯录结构体
- main函数中创建通讯录
- 封装添加联系人函数
- 测试添加联系人功能
C++核心编程
1.内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器制动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区的意义:
- 不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
1.1 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
- 代码区:
- 存放CPU执行的机器指令
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
- 全局区:
- 全局变量和静态变量存放在此
- 全局区还包括了常量区,字符串常量和其他常量也存放在此
- 该区域的数据在程序结束后由操作系统释放
总结:
- C++中在程序运行前分别为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放const修饰的全局常量和字符串常量
1.2 程序运行后
栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
1.3 new操作符
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
示例1: 基本用法
new一个变量
int * func()
{
int *a = new int(10);
return a;
}
int main()
{
int *p = func();
cout << *p << endl;
//释放堆区p
delete p;
return 0;
}
new一个指针
int * func()
{
int * arr = new int[10];
return arr;
}
int main()
{
int *arr = func();
for (int i =0;i<10;i++)
{
arr[i] = i+100;
}
for(int j=0;j<10;j++)
{
cout << arr[i] << endl;
}
delete[] arr;
}
2.引用
2.1 引用的基本使用
作用:给变量起名
语法:数据类型 &别名 = 原名
示例:
int mian()
{
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");
}
2.2 引用的注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
示例:
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;
}
2.2 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
示例:
//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 mySwap01(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 << endl;
cout << "b = " << b << endl;
mySwap02(&a,&b);
cout << "a = "<< a << endl;
cout << "b = " << b << endl;
mySwap01(a,b);
cout << "a = "<< a << endl;
cout << "b = " << b << endl;
}
2.4 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
示例:
//1.不要返回局部变量的引用
int & test01()
{
int a = 10;
return a;
}
//2.函数的调用可以作为左值
int& test02()
{
static int a = 10; // 静态变量,存放在全局区,全局区上的数据在程序结束后才会释放
return a;
}
int main()
{
int &ref = test01();
cout << " ref = " << ref <<endl; //第一次结果正确,是因为编译器做了保留
cout << " ref = " << ref <<endl; //第二次结果错误,是因为a的内存已经释放
int &ref2 = test02()
cout << " ref2 = " << ref2 <<endl;
cout << " ref2 = " << ref2 <<endl;
}
2.6常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
示例:
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);
return 0;
}
3 函数的提高
3.1 函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值得。
语法:返回值类型 函数名 (参数 = 默认值) {}
示例:
int func(int a = 10,int b = 20,int c = 30)
{
return a +b+c;
}
int main()
{
cout << func() << endl;
return 0;
}
3.2 函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调佣函数是必须填补该位置
语法:返回值类型 函数名(数据类型) {}
示例:
int func(int a ,int)
{
cout << "this is func" << endl;
}
int main()
{
func(10,10);
return 0;
}
3.3 函数重载
3.31 函数重载概念
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名相同
- 函数参数类型不用 或者 个数不用 或者 顺序不用
注意:函数的返回值不可以作为函数重载的条件
示例:
void func()
{
cout << "this is func" <<endl;
}
void func(int a)
{
cout << "this is (int)func" << endl;
}
void func(double a)
{
cout << "this is (doule)func" << endl;
}
int main()
{
func();
func(10);
func(3.14);
return 0;
}
3.3.2 函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
示例:
//函数重载注意事项
//1.引用作为函数重载的条件
void func(int &a)
{
cout << "func(int &a)调用" << endl;
}
void func(const int &a)
{
cout << "func(const int &a)调用" << endl;
}
//2.函数重载碰到默认参数
void func02(int a ,int b = 10)
{
cout << "func(int a ,int b = 10)调用" << endl;
}
void func02(int a)
{
cout << "func02(int a)调用" << endl;
}
int mian ()
{
int a = 10;
func(a);
func(10);
func02(10); // 存在二义性不用执行
func02(10,20);
return 0;
}
4 类和对象
C++面向对象的三大特征为:封装、继承、多态
C++认为万事万物皆对象,对象上有其属性和行为
例如:
人可以作为对象,属性有名字、年龄、身高、体重.... ,行为有走、跑、跳、吃饭、唱歌....
车也可以作为对象,属性有轮胎、方向盘、车灯....,行为有载人、放音乐、放空调....
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
4.1 封装
4.1.1 封装的意义
封装是C++面向对象三大特性之一
封装的含义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装的意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{ 访问权限: 属性 / 行为 };
示例1:设计一个圆类,求圆的周长
const double PI = 3.14;
class Circle
{
public:
double perimeter();
int width;
};
void Circle:: double perimeter
{
cout << (2*PI*width) << endl;;
}
int main ()
{
Circle c1;
c1.width = 5;
cout << c1.perimeter() << endl;
return 0;
}
示例2:设计一个学生类,属性有名字和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
class Student
{
public:
Student(string a,int b):name(a),id(b){};
void showStudent();
private:
string name;
int id;
};
void Student :: showStudent()
{
cout << "姓名:" << name << "学号:" << id << endl;
}
int main()
{
Student s1;
s1("黄龙",5210789);
s1.showStudent();
return 0;
}
封装的含义二:
类在设计时,可以把属性和行为放在不用的权限下,加以控制
访问权限有三种:
- 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 = 123456;
}
}
int main()
{
Person p;
p.m_name = "李四";
//p.m_Car = "汽车"; //错误,保护权限类外无法访问
//p.m_Password = 123; //错误,私有权限类外无法访问
func();
return 0;
}
4.1.2 struct 和 class区别
在C++中 struct和class唯一的区别就在于默认的访问权限不用
区别:
- struct 默认权限为公共
- class 默认权限为私有
class C1
{
int m_a;
}
struct C2
{
int m_a;
}
int main()
{
C1 c1;
//c1.m_a = 2; //错误 私有变量在外部无法访问
C2 c2;
c2.m_a = 2;
return 0;
}
4.1.3 成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制写权限
优先2:对于写权限,我们可以检测数据的有效性
封装联系案例
练习案例1:设计立方体类
设计立方体类(Cube)
求出立方体的面积和体积
代码:
class Cube
{
public:
Cube(int a = 0,int b = 0,int c = 0):m_L(a),m_H(b),m_W(c){};
void Area();
void Volume();
private:
int m_L;
int m_H;
int m_W;
}
void Cube :: Area()
{
int area = ((m_L * m_H + m_H * m_W + m_L *m_W)*2);
cout << area<< endl;
}
void Cube :: Volume()
{
int volume = m_L*m_H*m_W;
cout << volume << endl;
}
int main()
{
Cube c1(10,10,10);
c1.Area();
c1.Volume();
return 0;
}
练习案例2:点和圆的关系
设计一个圆形类(Circle) ,和一个点类(Point),计算点和圆的关系
代码:
4.2 对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始化设置以及对象销毁前的清理数据的设置
4.2.1 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始化状态,队其使用后果是未知
同样的使用完一个对象或者变量,没有及时清理,也会造成安全问题
C++利用了构造函数和析构函数解决上述的问题,这两个函数将对对编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称于类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候自动用构造,无需手动调用,而且只会调用一次
析构函数:~类名(){}
- 没有返回值也不写void
- 函数名称于类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁钱会制动调用析构,无须手动执行,而且只会调用一次
class Person
{
public:
Person(){
cout << "Person 构造函数的调用" << endl;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};
void test01()
{
Person p;
}
int main()
{
test01();
return 0;
}
4.2.2 构造函数的分类及调用
两种分类方式:
按参数分为:有参数构造和无参构造
按类型分为:普通析构和拷贝析构
三种调用方法:
括号法
显示法
隐式转换法
示例:
class Person()
{
public:
//无参构造函数
Person(){
cout << "Person 无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a){
cout << "Person 有参构造函数的调用" << endl;
age = a;
}
//拷贝构造函数
Person( const Person &p ){
//将传入得人身上的所有属性,拷贝到我身上
cout << "Person 拷贝构造函数的调用" << endl;
age = p.age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
int age = 0;
};
//调用
void test01()
{
//1.括号法
Person p1; //默认构造函数的调用
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数
//2.显示法
Person p1; //无参构造
Person p2 = Person(10); //有参构造
Person p3 = Person(p2); //拷贝构造
Person (10); //匿名对象 特点:当前行执行结束,系统会立即回收掉匿名对象
//3.隐式转换法
Person p4 = 10; //相当于 写了 Person p4 = Person(10);
}
int main()
{
test01();
return 0;
}
4.2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 已值方式返回局部对象
示例:
class Person
{
Person()
{
cout<< "This is 无参构造函数!" << endl;
}
Person(int a)
{
Age =a;
cout << "This is 有参构造函数!" << endl;
}
Person( const Person &p )
{
Age = p.Age;
cout << "This is 拷贝构造函数!" << endl;
}
~Person()
{
cout << "This is 析构函数!" << endl;
}
int Age;
};
//使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person p1(10);
Person p2(p1);
cout << "p2的年龄" << p2.Age << endl;
}
//值传递的方式给函数参数传值
void doword(Person p)
{
}
void test02()
{
Person p;
doword(p);
}
//已值方式返回局部对象
Person doword2()
{
Person p1;
return p1;
}
void test03()
{
Person p = doword2();
}
int main()
{
test01();
test02();
tset03();
return 0;
}
4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝函数,对属性进行值拷贝
构造函数调用规则然然下:
- 如果用户定义有参构造函数,C++不在提供默认无参构造函数,但是会提供默认拷贝函数
- 如果用户定义拷贝构造函数,C++不会在提供其他构造函数
4.2.5 深拷贝和浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
- 浅拷贝的问题 堆区的内存重复释放,解决方法:在拷贝函数时候定义一个新的堆区来存放拷贝过来的数据
示例:
class Person
{
Person()
{
cout<< "This is 无参构造函数!" << endl;
}
Person(int a)
{
Age =a;
Height = new int()
cout << "This is 有参构造函数!" << endl;
}
Person( const Person &p )
{
Age = p.Age;
cout << "This is 拷贝构造函数!" << endl;
}
~Person()
{
if(Height != NULL)
{
delete Height;
m_Height = NULL;
}
cout << "This is 析构函数!" << endl;
}
int Age;
int *Height;
};
4.2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2) ... {}
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
例如:
class A {}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身
析构的顺序和构造的顺序是相反的
示例:
class Phone
{
punlic:
Phone(string name):m_PhoneName(name){}
~Phone(){cout << "Phone析构" <<endl; }
string m_PhoneName;
};
class Person
{
public:
Person(string name,string pName):m_name(name),m_Phone(pName){}
~Person(){ cout << "Person析构" << endl; }
void playGame()
{
cout << "m_name" << "使用" << m_Phone.m_PhoneName << "牌手机" <<endl;
}
}
void test01()
{
Person p("张三","水果11")
p.playGame();
}
int main()
{
test01();
return 0;
}
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有成员共享同一个函数
- 静态成员函数只能访问静态成员变量
示例:
class Person
{
public:
Person(){}
static void func()
{
m_A = 100;
//m_b = 100; //错误 无法调用非静态成员变量,无法区分到底是哪个对象的m_b属性
cout << "This is 静态成员函数" << endl;
}
static int m_A; //静态成员变量
int m_b;
};
int Person::m_A = 0;
viod test01()
{
//1.通过对象访问
Person P;
P.func();
//2.通过类名访问
Person::func();
}
int main()
{
test01();
return 0;
}
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
class Person
{
public:
Person(){ mA = 0; }
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func()
{
cout << "mA:" << this->mA << endl;
}
};
int Person::mB = 100;
void test01()
{
//空函数占用内存空间为1个字节,
//因为他会给每个空对象也分配一个字节空间,是为了区分空对象在内存中的位置
//每个空对象也应该有一个独一无二的内存地址
Person p;
cout << "size of p = " << sizeof(p) << endl;
}
void test02()
{
//占用4个空间的内存大小
Person p;
cout << "size of p = " << sizeof(p) << endl;
}
int main()
{
test01();
return 0;
}
4.3.2 this指针概念
通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。this指针执行被调用的成员 函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
//1.当形参和成员变量同名时,可以this指针来区分
this -> age = age;
}
Person& PersonAddPerson(Person p)
{
this ->age += p.age;
//返回对象本身
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main()
{
test01();
return 0;
}
4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
示例:
//空指针调用成员函数
class Person
{
public:
void showClassName()
{
cout << "this is Person class" << endl;
}
void showPersonAge()
{
//防止代码被传入空指针而崩溃掉
if(this == NULL)
{
return;
}
cout << "age = " << age << endl;
}
int m_age;
};
void test01()
{
Person *p = NULL;
p->showClassName();
p->showPersonAge();
}
int main ()
{
test01();
return 0;
}
4.3.4 const修饰成员函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
示例:
class Person
{
public:
// this 指针的本质 是指针常量 指针的指向是不可以修改的
// const Person * const this;
// 在成员函数后面加const, 修饰的是this指向, 让指针指向的值也不可以修改
void showPerson() const
{
//m_A = 100; //错误无法修改因为加了const
this->m_B = 100;
}
int m_A;
mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable
};
//常对象
void test02()
{
const Person P; // 在对象前加const,变为常对象
//p.m_A = 100; //错误 无法修改常对象中无法修改普通变量
p.m_B = 100; // m_B是特殊值,在常对象下也可以修改
//常对象只能调用常函数,不可以调用普通函数
}
4.4 有元
生活中你的家有餐厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为friend
有元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
全局函做友元
class Building
{
friend void goodGay(Building * building)
public:
Building()
{
this -> m_SittingRoom = "客厅";
this -> m_RedRoom = "卧室";
}
public:
string m_SittingRoom ;
private:
string m_RedRoom;
};
void goodGay(Building * building)
{
cout << "好基友正在访问: " << building ->m_SittingRoom <<endl;
cout << "好基友正在访问: " << building ->m_RedRoom <<endl;
}
void test01()
{
Building b;
goodGay(&b);
}
int main()
{
test01();
return 0;
}
//类做友元
class Building;
class goodGay
{
public:
goodGay();
void visit();
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
friend class goodGay;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay:: visit()
{
cout << "好基友正在访问: " << building ->m_SittingRoom <<endl;
cout << "好基友正在访问: " << building ->m_RedRoom <<endl;
}
//成员函数做友元
class Building;
class goodGay
{
public:
goodGay();
void visit1();
void visit2()
private:
Building *building;
};
class Building
{
//添加成员函数做友元
friend void goodGay::visit1();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay:: visit1()
{
cout << "好基友正在访问: " << building ->m_SittingRoom <<endl;
cout << "好基友正在访问: " << building ->m_RedRoom <<endl;
}
void goodGay:: visit2()
{
cout << "好基友2正在访问: " << building ->m_SittingRoom <<endl;
//cout << "好基友2正在访问: " << building ->m_RedRoom <<endl; //无法访问
}
void test01()
{
goodGay gg;
gg.visit1();
gg.visit2();
}
4.5 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1 加号运算符重载
作用: 实现两个自定义数据类型相加的运算
//加号运算符重载
class Person
{
public:
//1.成员函数重载+号运算符
Pereson 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;
};
//2.全局函数重载+号运算符
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;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_A = 10;
Person p2;
p2.m_A = 10;
p2.m_A = 10;
Perspm p3 = p1+p2;
cout << "p3.m_A = " << p3.m_A << "p3.m_B = " << p3.m_b << endl;
}
- 总结1:对于内置的数据类型的表达式的运算符是不可以改变的
- 总结2:不要滥用运算符重载
4.5.2 左移运算符重载
作用:可以输出自定义的数据类型
class Person
{
public:
friend ostream & operator<<(ostream &output ,Person &p);
int m_A;
int m_B;
};
ostream & operator<<(ostream &output ,Person &p)
{
output << "m_A = " << m_A << " "<< "m_B = " << m_B;
return output;
}
void test01()
{
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p << endl;
}
int main()
{
test01();
return 0;
}
- 总结:重载左移运算符配合友元可以实现输出自定义数据类型
4.5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
class print
{
public:
print(int a = 0):m_Num(a){}
friend ostream & oparetor<<(ostream &output,print &p);
//重载前置++运算符 返回引用是为了对一个数据继续进行递增操作
print& operator++()
{
++m_Num;
return *this;
}
//重载后置++运算符
print& operator++(int)
{
print temp = *this
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream & oparetor<<(ostream &output,print &p)
{
output << m_Num;
return output;
}
void tset01()
{
print P;
cout << ++P << endl;
cout << P << endl;
cout << P++ << endl;
}
4.5.4 赋值运算符重载
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
class Person
{
public:
Person(int age){ m_Age = new int(age); }
//重载赋值运算符
Person & operator=(Person &p)
{
if(m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器提供的是浅拷贝,深拷贝解决内存重复释放的问题
m_Age = new int(*p.m_age);
return *this;
}
~Person()
{
if(m_Age != Null)
{
delete m_Age;
m_Age = NUll;
}
}
private:
int *m_age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 =p2 =p1;
cout << "p1的年龄为: "<< *p1.m_Age << endl;
cout << "p2的年龄为: "<< *p2.m_Age << endl;
cout << "p3的年龄为: "<< *p3.m_Age << endl;
}
int main()
{
test01();
return 0;
}
4.5.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
class Person
{
public:
Person(string name,int age)
{
this -> m_name = name;
this -> m_age = age;
}
bool operator==(Person &p)
{
if(this -> m_name == p.name && this -> m_age == p.age)
{
return false;
}
else return trun;
}
private:
string m_name;
int m_age;
};
void test01()
{
Person p1("Tom",18);
Person p2("Tom",18);
if(p1 == p2)
{
cout << "p1和p2相等" << endl;
}
else cotu << "p1和p2不相等" << endl;
}
int main()
{
test01();
return 0;
}
4.5.6 函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为伪函数
- 伪函数没有固定写法,非常灵活
网友评论