C++学习笔记
一、 编译环境及基本操作:
1.新建项目-win32控制台应用程序-空项目
2.代码区
3.解决方案解析
project7_17_38:项目目录(一个解决方案可以有多个项目(右键-设为启动项目))
.sdf:分析文件(每次打开项目时创建,可以删除)
.sln:解决方案(打开项目,不可删除)
.suo:用户设置(可以删除)
main:主函数,也称为入口函数
一个项目只有1个主函数
一个解决方案可以有多个主函数
编译:查找是否有语法错误,生成.obj(目标)文件,
生成-编译(Ctrl+F7)
输出窗口:查看错误原因。
运行:查看最终效果,将.obj文件链接成.exe,
调试-开始执行(Ctrl+F5)
引用头文件:
#include “文件名”(引用自定义文件):在项目目录下查找该文件,找不到
再到系统目录下查找,再找不到就报错。
#include <文件名>(引用系统文件):在系统目录下查找该文件,找不到就报错。
注释:不参与编译,方便程序员阅读代码。(70%)
单行注释:以回车结束
多行注释:/内容/ 快捷键:Ctrl+K+C
取消注释:Ctrl+K+U
例:
/作者:**
日期:2018/7/17
版本:1.0.0*/
/*
常量:程序中不能改变的量。1,3.1,‘a’(字符),“abc”(字符串)
变量:程序中可以改变的量,比如血量/等级/经验值等。
定义:数据类型 变量名;
变量名:合法的标识符。
1.由字母(a-z/A-Z)/数字(0-9)/下划线(_)其中至少一者组成。
例如:a,a3,a_3
2.开头不能是数字 比如3a(错误)
3.不能是关键字 比如:INt Int iNt(C++区分大小写)
4.人为规定:顾名思义(hp/lv/exp/hero_lv)
二、 数据类型:
整型/浮点型/字符型/布尔型
1.整型(用来表示整数):
int(普通整型):
在16位机器上占2个字节,
在32位以上机器上占4个字节。
short(短整型):占2个字节。
long(长整型):占4个字节。
long long(扩展性整型):占8个字节。
内存:以字节为单位(变量占内存)
机器码:
以二进制(0和1)存储。
一个字节用8位二进制表示,
第一位表示符号位(0表示正,1表示负)
short(-2^15 ~ 2^15-1):最大值二进制:01111111 11111111
=10000000 00000000 - 1 = 2^15 - 1
最小值:-2^15
2.进制转换
二进制转十进制:从右往左,每一位上乘以2的n次方,
n从0开始,依次递增1,求和。
十进制转二进制:依次除以2,直到商为0,余数从下往上读。
m进制转十进制:从右往左,每一位上乘以m的n次方,
n从0开始,依次递增1,求和。
十进制转m进制:依次除以m,直到商为0,余数从下往上读。
二进制与八进制:每3位二进制对应1位八进制。
二进制与十六进制:每4位二进制对应1位十六进制。
程序:十进制:(0-9)
八进制:(0-7):017 024
十六进制:(0-9,a-f(A-F)):0xab 0x1a
3.浮点型(用来表示小数默认为double类型):
float(单精度):占8个字节
double(双精度):占8个字节
long double(扩展性精度):占16个字节
2.%m.nf:n表示小数点位数
m表示字符宽度
如果实际字符宽度大于m,则正常显示
如果小于,m为正数,在前面补空格,m为负数,在后面补空格
3.浮点数的表示形式:1.小数形式
2.指数形式 3e2=3*10^2
4.字符型:char:占一个字节。
既可以表示小整数(-128-127),又可以表示字符
每一个字符都对应一个整数,称为ASCLL码值。
1.字符类型在参与运算时,提升为整型
‘A’-‘C’=-3;
2.大小写切换:大写-小写:ch+(‘a-‘A’)
3数字与字符的切换:0=‘o’:num+(‘o=0’)
4.转义字符:\字符:占1个字节
\八进制:\0
\十六进制:\x
5布尔类型(用来表示真假):
bool:占1个字节(true(1)和false(0))
1.布尔类型参与运算时,提升为整型
2.一切非零的数都为真
#include 输入输出流库
using namespace std;std是命名空间名
引用命名空间
1.全局引用(可能会造成命名空间污染)
(当前文件所有对象都可以使用)
2.局部引用:命名空间::
c语言的输入输出:scanf(),printf()
c++的输入输出:cin>>,cout<<
三、 运算符:
- 运算符运算法则:
算术运算符:+ - * / %(求余/取模)
1.不同精度的类型参与运算时,结果以精度高的为准。
int + double = double
2./:整型相除时,结果依然为整型。3/2 = 1
3.%:运算符左右都必须为整型。
判断偶数/奇数:x%2
拆分数字:198
个位:198/1%10 = 8
十位:198/10%10 = 9
百位:198/100%10 = 1
个位/十位/百位:a,b,c (c100+b10+c)
棋牌:card
1.花色:card / 0x10
2.点数:card % 0x10
红桃3:0x13
(花色:
红桃(0x11-0x1D)/黑桃(0x21-0x2D)/方块(0x31-0x3D)/梅花(0x41-0x4D))
2.点数:1-13
3.大小王(0x51,0x52)
红桃3:0x13)
自增自减运算符:
++:变量本身值+1;a++
–:变量本身值-1;a–
前置:++a:先自增再运算。
后置:a++:先运算再自增。
1.优先级:后置>前置
2.效率:前置>后置
关系运算符(结果为bool类型):> < >= <= ==(等于) !=(不等于)
逻辑运算符(式子左右为bool类型,结果为bool类型):(and or not)
逻辑&&(且):a&&b;当a和b同时为真时,结果为真,否则为假。
当a表达式为假时,则不计算b表达式。
逻辑或||:a||b;当a和b同时为假时,结果为假,否则为真。
当a表达式为真时,则不需要计算b表达式。
逻辑非!:!a:a为真,!a为假,a为假,!a为真
赋值运算符:=
1.左边必须为变量
2.优先级倒数第二
3.与其他运算符结合使用:+= -= *= /= %=
a+=1;a=a+1;
a-=b;a=a-b;
4.当表达式中有多个赋值运算符时,从右往左进行运算。
逗号运算符:表达式1,表达式2,…表达式n
1.逗号运算符的优先级最低
2.运算法则从左往右,最终结果为最后一个表达式的结果。
条件运算符(三目运算符):
?: a?b:c;当a为真时,执行b,否则执行c。
强转:强制类型转换
(要转换的类型)
(int)量;
位运算符(速度最快):以二进制的补码参与运算。
原码:将一个数转化为二进制。
反码:正数的反码是本身,负数的反码是除
符号位外全部取反(0变1,1变0)
补码:正数的补码是本身,负数的补码是
反码+1.
补码-原码:补码的补码就是原码。
运算流程:原码-补码(参与运算)-补码(运算结果)-原码(最终结果)
<<(左移):a<<1;a左移1位;a<<b;a左移b位;
将二进制的补码整体往左移n位,后面补0.(乘以2的n次方)
(右移):a>>1;
将二进制的补码整体往右移n位,正数补0,负数补1.
按位与&:a&b;(消除某些位数&0=0/保留某些位数&1)
将a和b的补码从右往左一一对应,同1为1,否则为0.
按位或|:a|b;
将a和b的补码从右往左一一对应,有1为1,否则为0.
按位异或:ab;
将a和b的补码从右往左一一对应,相同为0,不同为1.
按位非:a
将a的补码全部取反(0变1,1变0)
1.实现交换两个数的值:
1.第三方变量
2.加法
3.异或
2.不使用算术运算符,如何判断一个整数是奇数还是偶数?
(x&1) == 0?printf(“偶数”):printf(“奇数”);
3.不使用关系运算符,如何判断一个整数是正数还是负数?
x>>31?printf(“负数等于-1”):printf(“正数等于0”);
4.如何判断一个数二进制的第n位是1还是0?
(x>>(n-1))&1
x&(1<<(n-1))
2. 运算符结合性
- 运算符优先级
优先级:单目>双目>三目(运算符左右的式子个数)
算术运算符>(<< >>)>关系运算符>位运算符(&>^>|)
逻辑运算符(&&>||)>条件运算符>赋值运算符>逗号运算符
四、 语 句:
条件语句和循环语句
1、条件语句:
1.if语句:
if(条件1) 如果条件1为真,则执行语句1
{
语句1;简单语句:只有一条语句,可以省略{}
复合语句:有多条语句,不可以省略{}
}
else当条件1为假时,执行语句2
{
语句2;
}
if(条件1) 如果条件1为真,则执行语句1
{
语句1;简单语句:只有一条语句,可以省略{}
复合语句:有多条语句,不可以省略{}
}
else if(条件2) 如果条件2为真时,执行语句2
{
语句2;
}else 条件1和条件2都不满足时,执行语句3
{
语句3;
}
if语句实例:
if(s>0&&s<=10)
if(s>=3&&s<=6) 3 4 5 6
x=2;
else if(s>1||s>8) 2 7 8 9 10
x=3;
else 1
x=1;
else
x=0;
1.条件语句只执行一次
2.elseif和else根据情况可写可不写,
elseif可以无限次添加,但else只有一个。
3.if语句只会进入一个分支。
4.else匹配原则:与之前最近的没有匹配else的if匹配。
表达式:由常量/变量和运算符其中至少一种组成。
比如:a/a+3/3+2/3/a+b
常量表达式:由常量和运算符组成的式子。
1.计算表达式的值
2.与case后的常量表达式一一比较,如果相同,则执行case后的语句。
2.Switch(表达式)语句:
Switch(表达式)
{
Case 常量表达式1;
语句1;
Case 常量表达式2;
语句2;
Case 常量表达式n;
语句n;
}{}不能省略
注意:
1.case后的常量表达式不能相同
2.结束的标志:1.}2.berak(跳出switch)
3.default与位置无关
实例:
输入一个成绩,判断在0-59不及格–0-5
60-79:良好 --6 7
80以上:优秀 - 8-10
int grade;
cin >> grade;
switch (grade / 10)
{
case 6:
case 7:6/7表达式执行相同语句
cout << "良好" << endl;
break;
case 8:
case 9:
case 10:
cout << "优秀" << endl;
break;
default:0-5
cout << "不及格" << endl;
break;
}
2、循环语句(可能执行多次):
1.for循环:
for(表达1;表达2;表达3)当所有表达式都省略时两个分号都不能省略
{
循环体;循环体是简单语句时可省略大括号{}不写
}
表达式1:用来给循环变量初始化;int i=0; (可以省略)
表达式2:判断循环跳出的条件;(可以省略,但要加入转移语句,例如:break)
当条件为真,执行循环体,为假,跳出for循环。
表达式3:改变循环变量的值(步长);++i;i+=2;(可以省略)
For循环流程:
转移语句:
Break:
1.在switch中,跳出switch语句
2.在循环语句中,跳出当前这一层循环;
Continue:跳出当前这一次循环,执行下一次循环
实例:
//99乘法表
//11=1 。。。。 19=9
//21=2 22=4
//31=3 32=6 33=9
//
for (int i = 1; i <= 9; i++)
{
//打印一行
for (int j = 1; j <= i; j++)
{
cout << i <<"*" << j << "=" << i * j << "\t";
}
//换行
cout << endl;
}
2.While循环:
1.判断表达式真假2当表达式为真时,执行循环体,否则跳出循环(反复执行1.2)
While(表达式)
{
循环体;
}
实例:
int main()
{
//输入一个整数,从个位起,输出每一位上的数
//求逆数:1786-6871
//1786 //6871 =68710+1
//178 //687 =6810+7
//17 //68 = 6*10+8
//1 //6
int num,newNum = 0;
cin >> num;
while (num != 0)
{
//cout << num % 10 << endl;//负数求余:余数符号:被除数符号决定//值:绝对值求余
newNum = newNum*10 + num % 10; //-2 % 3 = -2 - 2 % -3 = -2;
num /= 10;
}
cout << newNum << endl;
3.do…while语句:
1执行一次循环体
2.判断表达式的值
3.如果为真,则执行循环体,否则跳出循环体
4.反复2和3
do
{
循环体;简单和复合都不能省略
}while(表达式);({}和;都不能省略)
实例:
int main()
{
//随机种子:只需要写一次
srand(time(NULL));
//9*9乘法表
int i = 1;
do
{
int j = 1;
do
{
cout << i <<"*" << j << "=" << i * j << "\t";
j++;
} while (j <= i);
i++;
cout << endl;
} while (i <= 9);
五、 数 组:
数组的定义:(数组:是一类相同数据类型元素的集合)
数据类型 数组名[数组大小];
数组大小必须为常量表达式:
1.常量与运算符结合的式子
2.const:只读的,修饰变量 const int a;//a是只读的变量
1.必须初始化
2.被const所修饰的变量不能被修改
3.宏定义 #define 宏变量 值
#define size 5 //在使用size的地方全部用5代替
数组的内存:数组大小*每一个元素的所占字节
数组的内存是连续的。如下图所示:
0 1 2 3 4 8 12
数组的初始化:{}
int n[5] = {1,2,3,4,5};
1.{},所有元素默认为0
2.{1,2,3},给部分元素赋值,其余元素默认为0
3.{1,2,3,4,5},给全部元素赋值,可以省略数组大小不写
数组赋值:给单个元素赋值。
数组元素访问:数组名[下标];
//下标:任意表达式,从0开始,到数组大小-1结束。
n[2];//第三个同学的数学成绩,int类型的变量
n[2]++;
n[2]&&n[3];
n[2] = 3;
*/
示例:
int num[5],count = 0;//count为计数器
//int count, num[5];
srand(time(NULL));
//随机5个数(0-100)存在数组中,计算偶数的个数
for (int i = 0; i < 5; i++)
{
num[i] = rand() % 101;//
if (num[i] % 2 == 0)
{
count++;
}
}
(c=getchar())!='\n'//
如何产生4个不同的数字?
1.判断随机的数与之前数组中的数是否相等,
如果相等则重新随机。
for (i = 0; i<4; i++)
{
int x = rand() % 10;
for (j = 0; j<i; j++) //检查是否有重复
{
if (num[j] == x)
break;
}
if (j >= i) //没重复
num[i] = x;
else
i--;
}
2.将10个数放到数组中,随机打乱,取前四个。
1.从键盘输入十个数,存在数组中,
求最大值和所对应的下标。
2.相邻两元素进行比较,将最大值移到最后一个位置。
//数组越界:当数组下标>=数组大小时
//使用非法内存:编译器没有分配给该变量使用的内存
实例:
int main()
{
//定义一个数字存放0-9之间的所有数字;
int s[4];//定义数组存放随机打乱的num[]数组的前四个
srand(time(NULL));
//把0-9之间数字放入数组num[]中
int num[10] = {0,1,2,3,4,5,6,7,8,9};
for(int i= 0; i <=9; i++ )
{ cout << num[i] << "\t"; }
cout << endl;
//随机打乱数组num[]中的值,给两个随机下标,交换数值;
for (int k = 1; k < 1000; k++)
{
int num1 = rand() % 10, num2=rand() % 10;
if (num1 != num2)
{
num[num1] = num[num1]^ num[num2];
num[num2] = num[num1] ^ num[num2];
num[num1] = num[num1] ^ num[num2];
}
}
//for (int k = 0; k < 10; k++)//随机打乱数组num[]中的值,给一个随机下标,从第一个数组的值开始交换数值;
for (int j = 0; j <4; j++)//取前四个num[]的值并输出
{
s[j] = num[j];
cout << s[j] << "\t" ;
}
cout << endl;
return 0;
}
1.冒泡排序:(泡往上冒(从小到大)/泡往下冒(从大到小))
1.相邻两元素进行比较,如果前者>后者,则交换两个数,
直到排出这一轮剩下的数中的最大(最小)值。
2.轮数:(数组大小-1)次
3.外层循环是轮数,里层循环是这一轮的比较次数
实例:
for (int i = 1; i <= 9; i++)//控制轮数
{
//将相邻两个元素比较,把最大的移动到最后
for (int j = 0; j <= 9 - i; j++)
{
if (num[j] >= num[j + 1])
{
num[j] = num[j] ^ num[j + 1];
num[j + 1] = num[j] ^ num[j + 1];
num[j] = num[j] ^ num[j + 1];
}
}
}
2.插入排序:(以从小到大为例)
1.将一个数插入到有序数列中,从后往前比,
如果大于某一个值,则位置确定,否则与前面的数交换,继续比较。
2.轮数:把数组看成只有1个数的有序数列,其余数组大小-1个数
当成是插入的数,轮数:数组大小-1.
//外层循环,控制轮数
for (int i = 1; i <= 9; i++)
{ //里层循环
for (int j = i; j>0; j--)//j初始值:为插入元素的下标
{
if (num[j]>=num[j - 1])
{
break;//跳出循环
}
else
{
num[j] = num[j] ^ num[j - 1];
num[j-1] = num[j] ^ num[j - 1];
num[j] = num[j] ^ num[j - 1];
}
}
}
3.选择排序: (以从大到小为例)
1.每轮选出当前剩下的数中的最大数,与
第i个元素交换,i从0开始,每次+1.
1 5 2 3 4
第一轮:5 1 2 3 4
第二轮:5 4 2 3 1
第三轮:5 4 3 2 1
第四轮:5 4 3 2 1
2.轮数:数组大小-1
实例:
for (int i = 0; i <= 8; i++)
{ //里层循环
int maxi_cur = i;//当前所剩数的最大值下标
for (int j =i+1; j < 10; j++)
{
if (n[j]>n[maxi_cur])
{
maxi_cur = j;
}
}
//将最大值与第i个元素交换
if (maxi_cur != i)
{
n[i] = n[i] ^ n[maxi_cur];
n[maxi_cur] = n[i] ^ n[maxi_cur];
n[i] = n[i] ^ n[maxi_cur];
}
}
4.字符数组: 元素类型为char类型
char 数组名[数组大小];
初始化
1.{}
2.字符串,可以省略{}不写 “abcd”
字符串以’\0’结尾.
输出字符数组名:1.输出字符串,直到’\0’结束
5.二维数组:多个一位数组。
定义:数据类型 数组名[行大小][列大小];
内存:行数列数单个元素所占字节;
二维数组的内存是连续的,按行存储;图
二维数组的初始化:
1.{}给部分元素/全部元素赋值时,可以省略行大小不写
给部分元素赋值时:行大小以最接近元素个数的列数的倍数填充
2.{ {},{},{} };
元素的访问:数组名[行下标][列下标];
第三行第二列:a[2][1]
字符数组:
char ch[3][6]={“abcdd”,”efgh”,”ijkl” }
1.字符串拷贝
1.将src中的元素一一赋值给dest的元素,直到’\0’
1.for (int i = 0; (dest[i] = src[i]) != '\0'; i++);
2. int i = 0;
while ((dest[i] = src[i]) != '\0')
i++;
2.库函数实现:1.目的字符串的数组名 2.源字符串的数组名
strcpy(dest, src);//字符串拷贝
2.字符串连接
1.方法实现:
//1.找到要连接的位置
int i = 0;//i表示’\0’的下标
while (dest[i] != ‘\0’)
i++;
//2.连接
for (int j = 0;(dest[i + j] = src[j]) != ‘\0’; j++);
2库函数实现:
1.被连接的字符数组名 2.连接的字符数组名
strcat(dest,src);//字符串连接
3.字符串比较 ························
1.方法实现:
int value = 0;//表示两个字符串相等
int i;
for (i = 0; dest[i] != '\0' && src[i] != '\0'&&dest[i] == src[i]; i++);
///1.不相等 2.最后一个
if (dest[i] < src[i])
value = -1;
else if (dest[i] > src[i])
value = 1;
cout << value << endl;
2. 库函数实现:
strcmp("ab\0", "ab");
4.子串在母串中出现的次数
char motStr[30] = "aaababaa";
char subStr[5] = "aa";
//1.遍历母串
int count = 0;
for (int i = 0; motStr[i] != '\0';i++)
{
//2.遍历子串
for (int j = 0; motStr[i + j] == subStr[j]; j++)
{
//判断子串对应的j+1下标如果为'\0',则表示已经找到
if ('\0' == subStr[j + 1])
{
count++;
break;
}
}
}
cout << count << endl;
return 0;
}
杨辉三角:
int main()
{
//杨辉三角
int n[SIZE][SIZE];//SIZE:宏变量
for (int i = 0; i < SIZE;i++)
{
for (int j = 0;j <= i;j++)
{
//左右两边数都为1
if (0 == j || j == i)
{
n[i][j] = 1;
}
else
{
n[i][j] = n[i - 1][j - 1] + n[i - 1][j];
}
}
}
//打印输出
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j <= i; j++)
{
cout << setw(3) << n[i][j];// setw(3)控制每个数字的宽度
}
cout << endl;
}
六、 函 数:
概念:对特定的功能进行封装
定义:由返回值,函数名,参数表和函数体组成。
返回值类型 函数名(形式参数表)
{
函数体
}
输出函数输出字符串“hello game”
void 空类型
1.确定返回值类型
2.确定函数名
3.确定参数表,参数表可以省略,但是括号不能省
4.参数表之中的参数用逗号隔开
调用:函数名(实参表)
注意:C++函数体内不能再定义其他函数,函数是并行的。
不能嵌套定义,可以嵌套调用
函数调用时:实参表和形参表类型,数量一一对应。
实参可以是常量,变量,表达式
- 作为函数语句
Printf(“hello\n”) - 函数表达式
Int num=sum(a+3,3); - 作为其他函数的实参;顺序:从内往外调用;
outputInt(sum(10, 9)); - 函数的调用流程:
(调用的地方:主调函数 被调用的函数:被调函数) - 从主调函数找到被调函数
- 系统给形参分配临时内存,将实参的值拷贝(赋值)给形参
- 执行函数体,有返回值将返回值带回主调函数
- 系统回收形参的临时内存
- 回到主调函数
声明:返回值类型 函数名(参数表);
写在调用之前就可以
形参和实参的区别:
1、 形参出现在函数定义时,实参出现在函数调用时。
2、 形参只在函数调用之后被分配内存,调用完释放形参内存。
3、形参的作用域只在函数内,出被调函数释放,实参进入被调函数后无法使用
七、 指 针:
1.概念:为了方便访问内存中的内容,给每一个内存单元编号,把这个编号称为地址,也就是指针。
2.定义:(也是一种数据类型)
类型 指针名
(指针指向的类型 * )指针名;(:标注这个变量为指针)
//定义了一个int类型的变量,变量名叫p
//p指向int类型的数据(p表示int类型数据的地址)
int* p;
//定义了一个int类型的变量,变量名叫p1
//p1指向int类型的数据(p1表示int类型数据的地址)
//该数据指向int类型的数据
int p1;
//定义了一个大小为5的数组,数组元素都为short类型
//数组名为p2
//
short p2[5];
3.指针的内存:
所有的指针都占四个字节;
4.指针的赋值:
&:取地址符:&(&变量)
解析引用符:只能跟指针(表示取指针指向的内容)
1.用指向类型的数据的地址赋值
int hp = 100;
//定义了一个int类型的变量p-
//p指向int类型的数据hp(p表示int类型数据的地址)
int p=&hp;
int* p1=p;
2.用相同类型的指针赋值
int* p1=p;
3.直接用地址赋值
int* p4 = (int*)0x12abcd89;
4.用字符串给字符指针赋值
char* p = "abcd";
5.用数组名赋值
int n[5];
int* p4 = n;
6.指针没有明确指向时,赋值为空
&,可以抵消,&不能抵消.
解析引用符:取内容字节数 = 指针指向的数据的字节数
short:short int:
使用cout输出char型时,
从这个地址开始直到\0的字符串全部输出.
指针练习及解析:
一、
int a = 3;//定义了一个名为a的int型变量,并给它赋初值为3
int p = &a;//定义了一个int*型的指针p,p指向a
*p = 4;//把整数4赋值给p指向的内容(实际上为a),p的指向的a变为4
cout << "a=" <<a << endl;//输出a=4
(*p)++;//*p指p指向的a,所以(*p)++相当于a++,因此a=4++=5
int b = 4;//定义了一个名为b的int型变量,并给它赋初值为4
int c = b**p;//定义了一个名为c的int型变量,并给它赋初值为b**p;
//b**p=b*(*p),*p指p指向的a,而a=5,所以b**p=b*5=4*5=20;
cout << "*p=" << *p << endl;//输出*p=5;*p指p指向的a,即a=5;
cout << "a=" << a << endl;//输出a,a=5;
cout << b << c << endl;//输出b,b=4;输出c,c=20;
int** p1 = &p;//定义了一个名为p1的int**型变量,p1指向p;
**p1 = c++;/*把c的值赋给**p1指向的内容,即*p指向的内容,即a,所以a=c=20;然后c++ c=21;*/
*p1 = &b;//把b的地址赋给*p1指向的内容,即p指向b;
(**p1)++;//**p1指向的内容自增,即b自增,即b=5;
p = &c;//把c的地址赋给指针p,p指向c;
*p = a***p1;//把a***p1赋值给*p指向的内容,即c,
//a***p1=a**(*p1)=a**p=a*(*p)=a*c=20*21=420;
cout << a << b << c << endl;//输出a,b,c的值,分别为:20 5 420
cout << *p1 << endl;//输出C的地址;p=&c
cout << &c << endl;//验证*p1是否是c的地址
cout << *p << endl;//输出*p指向的内容,即c,所以输出420
二、
char ch[5] = “abcd”; //定义了一个大小为5的char型数组ch,并给它的元素赋值为abcd;
char* p = &ch[1];//定义了一个char型的指针p,p指向ch[1];
charp1 = &p;//定义了一个char型的指针p1,p1指向p;
cout << p1 << endl;//输出p1指向的内容,即p,p为char型,所以输出:bcd
ch = (p1)++;//把p1指向的内容赋值给ch指向的内容,然后**p1指向的内容自增
//*ch指向ch[0]:a;**p1指向ch[1],所以,ch[0]=ch[1]=b;ch[1]++=c;
*p1 = &ch[3];*p1指向p,p = &ch[3];p指向ch[3]
cout << *p + 3 << endl;//*p指向ch[3],所以输出‘d’+3=100+3=103
*p1 = &ch[2];//*p1指向p,p = &ch[2];p指向ch[2]
cout << *p1 << endl;//p1指向p,p为char型,所以输出cd
(*p)++;//p指向的内容为ch[2];所以ch[2]=c++=d;
cout << p << endl;//输出p指向的内容,即ch[2],即d
cout << ch[2] << endl;//输出ch[2],即d
cout << ch[2] + 1 << endl;//输出ch[2]+1,即d+1,提升为整型,所以输出100+1=101;
cout << p << endl;//应为p为char型,并且p指向ch[2],所以输出dd
三、
char ch[5] = “ijkl”;//定义了一个大小为5的char型数组ch,并给它的元素赋值为ijkl;
char* chArray[3] = { ch, &ch[1], &ch[3] };//定义了一个大小为2的char型数组chArray,
//并且它的元素分别指向ch, &ch[1], &ch[3];
char** pArray = chArray;//定义了一个char**型变量pArray ,pArray指向chArray数组的首元素;
int i = 1;//定义了一个int型变量i,并给它赋值为1;
ch[i++] = (**pArray) + 3;//把(**pArray) + 3赋值给ch[i++],ch[i++]即为ch[1],
//,**pArray指向的内容为ch[0];ch[1]=ch[0]+3=l;并且i++,i=2
pArray = &chArray[2];//把chArray[2]的地址赋值给pArray,pArray指向chArray[2];
cout << pArray << endl;//输出pArray指向的内容,即&ch[3],为char型,所以输出l
cout << &ch[i] << endl;//输出&ch[i],i=2;所以输出&ch[2],又因为&ch[2]为char型,所以输出kl
pArray = &ch[0];//把ch[0]的地址赋给pArray指向的内容,即chArray[2]指向ch[0];
(**pArray)–;//指向的内容为ch[0],所以ch[0]–,ch[0]=h;
cout << pArray << endl;//输出pArray内容,即ch[0],所以输出h
pArray = &chArray[1];//把chArray[1]的地址赋给pArray,pArray指向chArray[1];
cout << **pArray << endl;//**pArray 指向的内容为chArray[1],所以输出chArray[1]
//chArray[1]为char型,所以输出jkl
cout << ch << endl;//ch为char*型,所以输出hjkl
cout << **pArray + 5 << endl;//*pArray指向ch[1],所以输出’j’+5,提升为整型;所以等于113
cout << (pArray)[1] << endl;//pArray所指内容向右偏移1,所以输出k
5.指针的运算:
1.加减法:指针加上或者减去一个整数n,表示指针偏移n个单位.
(单位 = 指针指向的类型所占字节)
int p;
p+1;//p+1偏移一个单位 = 4个字节
short p;
p-1;//p-1 = 向左4个字节
2.指针 - 指针:相差的单位数
3.指针的自增自减:
p++;
p–;指针本身偏移1个单位;
4.指针[]:p[1],p[-1];
以p为首地址的第n+1个元素;
p[n] = *(p+n);
数组名和指针除了自增自减和赋值外,无区别.
大端存储(与阅读习惯一致):数据的高位存在内存的低地址,
数据的低位存在内存的高地址.(读取数据:低地址 - 高地址)
小端存储(win32)(与逻辑习惯一致):数据的高位存在内存的高地址,
数据的低位存在内存的低地址.(读取数据:高地址 - 低地址)
nullptr:强指针(只能赋值给指针)
6.数组指针:
数组指针:指向数组的指针.
1.数组指针的定义:
元素类型(*数组指针名)[列数n];指针指向列数为n的一维数组
数组指针的赋值:
1.一维数组名的地址
2.二维数组名
2.数组指针的偏移:
p+1:偏移一行(1个单位 = 每个元素所占字节 * 列数)
p++:
p[n] = (p+n)
指针数组:数组的元素为指针.
char n[5];
含义上,n是&n[0]
值:n = n[0] = &n[0][0]
/cout << n << endl;
cout << n[0] << endl;
cout << &n[0][0] << endl;/
定义了一个数组指针p,p指向列数为4,
元素类型为int型的一维数组.
n是{n[0],n[1],n[2]}的数组名,n等于&n[0]
n[0]是{n[0][0],n[0][1],n[0][2],n[0][3]}的数组名
n[0]是&n[0][0]
int(*p)[4] = n;//n等于&n[0]
定义了一个数组指针p1,p1指向列数为5,元素类型为char型的一维数组
char(*p1)[5];
1.数组:数组大小与数组名不用括号分开
2.指针:指向的类型 * 指针名
定义了一个指针p2,指针指向数组指针
该数组指针指向列数为4,元素类型为int类型的一维数组
int(**p2)[4] = &p;
数组:元素类型 数组名[数组大小]
定义了一个大小为5的数组p3,
元素类型为数组指针类型,该数组指针
指向列数为4,元素类型为int型的一维数组
int(*p3[5])[4] = {p,p,p,p,p};
含义:n[0]是第一行的数组名,是&n[0][0],为int*型
cout << n[0] << endl;
cout << &n[0][0] << endl;
int* p = &n[0][0];
7.多维数组:
数据类型 数组名[维数1][ 维数2][]……[ 维数n];
int n[3][4][5];
指向一维数组及以上的指针都是数组指针.
元素类型 (指针名)[维数2]…[维数n]
typedef:给类型取别名。(写在函数外,一般写在函数前)
typedef 类型 别名;
观察什么类型可以将typedef去掉
void:空指针类型(不确定类型的指针)
1.可以指向任意类型的数据
2.因为没有明确的指向,所以不能偏移和取内容。
指针与const的结合:
(非const可以给const赋值,const不能直接给非const赋值)
1.常量指针:表示指针指向一个常量
(const 指向类型)指针名;const int n;
(指向类型 const)指针名;int const n;
常量指针不能改变指向的内容,但是可以改变指向。
2.指针常量:指针本身被const修饰的变量,是只读的
指向类型* const 指针名
1.必须初始化
2.指针常量不能改变指向,但是可以改变指向的内容
3.常量指针常量:
1.必须初始化
2.既不能改变指向,也不能改变指向的内容
区分常量指针和指针常量:
const在之前是常量指针,在后就是指针常量
8.内存区域的划分:
1.常量区(不能被修改):
常量(1,100,1.1,‘a’,“abcd”),字符串
程序开始时系统分配内存,程序结束时系统自动回收内存.
2.全局区(静态存储区):
全局变量(定义在函数外的变量)和
静态(static)变量(static 类型 变量名;)
1.只初始化一次,默认为0
2.内存只有一份
程序开始时系统分配内存,程序结束时系统自动回收内存.
3.栈区:局部变量(定义在函数内的变量)
进入函数时系统分配内存,函数结束时系统回收内存.
4.堆区:由程序员手动申请,手动释放.(申请-释放-置空)
C语言(函数):
1.形参:申请的字节数 2.返回值:申请内存的首地址
void *malloc( size_t size );
函数返回一个指向num 数组空间,
每一数组元素的大小为size。
如果错误发生返回NULL。
void *calloc( size_t num, size_t size );
释放内存:free(内存首地址);
C++(运算符):
int* p = new int;//返回申请内存的首地址
int* p = new int(元素初始化);//给这段内存存入值
int* p = new int[数组大小];//申请连续的内存
delete 内存的首地址;//释放1个指向大小的内存
delete[] 内存的首地址;//释放一段连续的内存
申请:
int** pMap = new int*[h];//申请内存(动态二维数组)
for (int i = 0; i < h; i++)
{
pMap[i] = new int[w];
}
释放:
for (int i = 0; i < h; i++)//释放内存
{
delete[] pMap[i];
pMap[i] = NULL;
}
delete[] pMap;
pMap = NULL;
全局变量和全局static变量区别:
1.生存周期相同,从程序开始分配内存,程序结束回收内存.
2.作用域不同,全局变量可以作用于所有文件,需要extern声明
全局static变量只能作用于当前所在文件.
局部变量和局部static变量区别:
1.生存周期不同,局部变量进入函数时系统分配内存,函数结束时系统回收内存.
局部static变量程序开始时系统分配内存,程序结束时系统自动回收内存.
2.作用域相同.(函数内)
9.指针补充
1.内存泄漏:内存没有释放,指针改变了指向
2.野指针(坏指针):释放了内存,但指针没有置空
使用指针:
1.指针必须有指向(没有赋值和指向NULL为两个概念)
2.数组越界
引用(实质上是指针)的定义:给变量取别名.
&:引用符
类型& 变量名;例如:int& a;
1.必须初始化
2.不能改变引用的对象
常引用(不能改变被引用对象的值):引用的是一个常量.
const 类型& 变量名;
指针和引用的区别:
1.引用必须初始化,指针可以不初始化.
2.引用不能赋值为空,指针可以赋值为空.
3.引用与被引用对象共享一段内存,指针有独立的内存.
4.引用不能改变引用的对象,指针可以改变指向.
动态一维数组:手动申请了5个int类型大小的内存,内存连续
p指向内存的第一个元素
int* p = new int[5];
delete[] p;
p = NULL;
cout << *p << endl;
p = new int[2];
delete[] p;
p = NULL;
动态申请二维数组内存(行与行之间内存不一定连续)
申请3行5列
1.申请3个int*类型大小的内存,
保存之后的每一行的首地址
int** p1 = new int*[3];
for (int i = 0; i < 3; i++)
{
p1[i] = new int[5];
}
for (int i = 0; i < 3; i++)
{
delete[] p1[i];
p1[i] = NULL;
}
delete[] p1;
p1 = NULL;
八、头文件:
1.文件管理:
头文件-右键-添加新建项-.h文件
2.引用自定义文件:用""引用
引用头文件的意义:整个头文件替换到引用处
3.项目文件夹分类
引用带路径的头文件:/
引用根目录上一层的路径:…
4.筛选器:方便在解决方案面板管理
(保持与外部文件夹同步)
注意:
1.将函数的声明写在.h文件中,将函数的定义写在.cpp文件中
(函数的声明可以多次,函数的定义只能一次)
(最好.h和.cpp文件同名)
2.全局变量的声明也放在.h文件中,定义写在.cpp文件中
(extern时给变量赋值变为定义,可能报重定义错误)
3.宏定义可以写在头文件中,定义多次不会报错,
后面的定义覆盖前面的定义.
1.头文件不进行编译,源文件分别编译
2.无法解析的外部符号:
函数:只写了声明没有写定义
无法解析的外部符号
变量:只写了声明没有写定义
九、函数补充:
函数的形参:
1.形参为数组时,会被弱化成指针
一维数组:
1.int n[10],10不代表数组的大小.
2.int n[];
3.int n[],int size;//size表示数组的大小
4.int* n;
5.int* n,int size;//推荐
2.二维数组:被弱化成数组指针
1.int n[3][10];
2.int n[][10];//不能省略列大小
3.int n[][10],int row;//row指行大小
4.int(*p)[10];//
5.int(*p)[10],int row;//推荐,加上行大小
3.形参为被const修饰时:
1.修饰普通类型,防止形参被修改
2.修饰指针时,常量指针(防止指向内容被改变)
3.修饰引用时,常引用(防止被引用对象的值被改变)
返回值:不能返回栈区的地址或者引用.
1.返回值为指针:
2.返回值为引用:既可以作为左值,又可以作为右值.
作为左值时:被引用对象被赋值.
作为右值时:1.给普通类型赋值:将值赋值给变量
2.给引用类型赋值时:该引用类型也引用该变量
特殊情况:返回常引用(防止通过返回值修改被引用对象的值)
函数:
优点:可以重复使用,节省空间,使结构更清晰.
缺点:查找函数,时间上的消耗.
短小(没有循环)且频繁使用的函数:
1.C语言:宏定义函数(宏替换)
#define 宏定义名(形参表:不需要类型) 函数体;
1.(宏替换),整体思维,注意()
2.不进行类型检测
3.传递类型
4.宏定义函数有多行时,每一行末尾用\结束
(\紧跟换行符)
2.C++:内联函数(函数体替换到函数调用处)
以空间换取时间的函数.
在函数定义前面加上inline关键字.
4.函数重载:
函数名相同,形参不同.(类型/顺序/个数其中至少一者不同)
1.参数传递时,只能低精度往高精度转化,否则进行强转
2.返回值类型不同,不能称为重载.
报错:无法重载返回类型区分的函数.
3.仅参数名不同,不能称为重载.
函数在C++内部编译:函数名_形参类型1_形参类型2
sum_int_int
C语言不支持重载.
5.函数缺省(参数缺省):形参有默认的实参值.
形参类型 变量名 = 默认值;
缺省顺序:从右往左;
参数缺省写在函数声明中;
int sum(int a = 1,int b = 2);//正确
int sum(int a,int b = 2);//正确
int sum(int a = 1,int b);//错误
1.不传递缺省实参,以默认值为准
2.传递实参,实际实参覆盖默认实参
6.函数指针:指针指向一个函数.(函数占内存)
函数指针(是一种数据类型)的定义:
返回值类型 (*函数指针名)(形参表(不需要变量名));
//pfunc指向返回值类型为int类型,形参为int,int的函数.
int(*pfunc)(int,int);
指针函数:返回值为指针的函数.
赋值:
1.&函数名(都表示指向函数)
2.函数名(推荐)
使用函数指针调用函数:
函数名(实参表);
1.(*函数指针)(实参表);
2.函数指针(实参表);(推荐)
7.const的至少三种用法:
指针常量:int* const p;//必须初始化
常量指针:const int* p;
常量指针常量:const int* const p;
常引用:const int& a = b;
const修饰普通类型的变量:表示只读
8.递归
1.斐波那契数列:1 1 2 3 5 8 13 21…
求第n项.
fab(n) = fab(n-2)+fab(n-1)
2.求!n:n的阶乘
3.猴子吃桃:第一天猴子吃了桃子总数的一半加1个,
第二天吃了剩下桃子数的一半加1个,按照这种吃法,
第10天时,发现只剩一个桃子,请问第一天有多少桃子?
第二天 = 第一天/2 - 1
第一天 = (第二天 + 1)*2
peach(day) = (peach(day+1) + 1) * 2;
递归:函数直接或者间接的调用自身.
递推:函数调用的过程.
回推:返回的过程.
当递归跳不出来时,栈溢出(stack overflow)
1.找规律 fab(n) = fab(n-2)+fab(n-1)
2.找跳出条件
if (1 == n || 2 == n)
return 1;
//mul(n) = n*mul(n - 1);
int peach(int day)
{
if (10 == day)
return 1;
return (peach(day + 1) + 1) * 2;
}
int mul(int n)
{
跳出条件
if (1 == n)
return 1;
return n * mul(n - 1);
}
int fab(int n)
{
//跳出条件
if (1 == n || 2 == n)
return 1;
//规律
return fab(n - 1) + fab(n - 2);
}
int fab(int n)
{
//如果n为1和2,返回1
if (1 == n || 2 == n)
return 1;
//第n项等于前两项之和
int a = 1, b = 1;//a表示n的前两项,b表示n的前一项
for (int i = 3; i <= n;i++)
{
int t = b;
b = a + b;
a = t;
}
return b;
}
函数:1.不能出现功能重复的代码
2.不要通过修改数据
项目三部曲:
1.初始化:数据的准备(执行一次)
2.更新:数据的改变(循环)
3.绘制:最终效果的显示(循环)
//防止头文件被重复引用
#ifndef _HERO_ //--如果没有定义
#define _HERO_ //--定义
*****
#endif
十、自定义类型:
结构体/联合/枚举(由基本数据类型组成)
- 结构体:
概念:一类具有相同属性和行为的事物的封装
英雄:属性:血量/等级/蓝/经验…
行为:移动/攻击…
骑车:属性:颜色/型号/速度…
行为:移动/加速/停止…
1.结构体的定义:
//struct:关键字,表示是一个结构体
struct 结构体类型名//类型名首字母大写
{
数据成员1;//数据类型+变量名
数据成员2;
数据成员n;
…
}
2.内存:结构体对齐
以所有类型中最大内存为单位分配字节.
4+4+4+4(hp)+4(lv)=20
定义结构体时,内存从小到大定义成员
3.结构体变量的定义:
结构体类型名+变量名;
4.初始化:{}//按照成员的顺序一一赋值,中间用逗号隔开
5.赋值:
1.{}
2.单独给成员赋值:
成员访问:成员选择符(.)
6.结构体类型指针的定义
结构体类型 * 指针名
Hreo* p=&hero;//p指向hero
通过结构体类型指针访问成员:1.(*p).lv
2.成员选择符(->):指针->成员名
7.结构体数组:
结构体类型 数组名[数组大小];
-
联合类型
定义:union 联合类型名
{
联合成员1;//数据类型+变量名
联合成员2;
};
内存:内存对齐(所有成员共享一段内存)
-
枚举类型:(表示状态值.)
方向的状态:上下左右
游戏状态:开始/暂停/游戏中/结束
物品类型:药品/装备
定义:enum 枚举类型
{
状态值1,//UP(推荐)/up
状态值2,
…
状态值n
};
内存:枚举变量为一个int类型的大小,
每一个枚举状态值都对应一个整数,
如果没有给枚举状态赋值,从0开始,依次+1.
如果给其中某个枚举状态赋值,从此之后的所有状态:基础上+1.
例如:
enum Dir
{
UP,
DOWN,
LEFT,
RIGHT
};
-
读取/存储文件:
1.存储文件:void saveFile(int** pMap, int row, int line, int level)
{
//1.文件路径 2.模式写入wb+ 读取rb+
//printf(“map_%d.txt”, level);
char fileName[20];
sprintf(fileName, “Map/map_%d.txt”, level);//printf输出到控制台,sprintf输出到字符数组中
FILE* pfile = fopen(fileName, “wb+”);//打开1个文件,如果没有,则自动创建
//写入:1.被写入的数据的首地址 2.1个数据的大小
//3.数据的个数 4.FILE*
for (int i = 0; i < row; i++)
{
//fwrite会偏移count个size大小的内存
fwrite(pMap[i], sizeof(int), line, pfile);
}
//关闭文件
fclose(pfile);
}
2.打开(读取)文件:
void readFile(int(*pMap)[10], int row, int line, int level)
{
char fileName[20];
sprintf(fileName, "Map/map_%d.txt", level);
//1.打开文件
FILE* pfile = fopen(fileName, "rb+");
fread(pMap, sizeof(int), row * line, pfile);
fclose(pfile);
}
//exit(0);//退出程序
十一、类:
- C语言:面向过程,以函数为核心.
- C++语言:面向对象,以类为核心.
1.类的概念: 一类具有相同属性和行为的事物的封装.
2.类的特性: 封装/继承/多态
//声明和定义各一个文件(.h和.cpp同名)
3.类的定义:
class 类名
{
成员1;//数据成员和成员函数
成员2;
};
class Hero //Hero是一种数据类型
{
public://:冒号
Hero();//默认普通构造函数
Hero(int hp);//带参构造函数
Hero(const Hero& other);//拷贝构造函数
private:
int lv;
int exp;
public:
int hp;
void pk();
void die();
};
4.struct和class的区别:
1.struct是C语言的,而class是C++的.
在C++中struct和class都能用,
在C语言中只能用struct.
2.成员访问权限:
struct默认为public,
class默认为private.
访问权限:
public:公有的,成员可以在类中和类外访问.
protected:保护的,成员可以在类中和子类中访问.
private:私有的,成员只能在类中访问.(默认)
1.三种访问权限不一定全部写.
2.数据成员一般指定为私有的.
成员函数一般指定为公有的.
3.重复访问权限不报错
5.类的对象(变量)创建:
1.类名 对象名;Hero hero;
2.new 类名;new Hero;
普通全局函数和类的成员函数的区别:
类的成员函数必须通过对象调用.
每一个成员函数中:this指针(可省略不写)
this指向当前调用函数的对象
6.类的四大默认成员函数:
1.普通构造函数(默认:没有形参)
1.没有返回值类型
2.函数名与类名完全相同
3.普通构造函数可以重载(普通构造函数可以有多个)
调用:给对象分配内存时调用.
(1.类名 对象名; 2.new 类名;)
调用无参:1.类名 对象名; 2.new 类名; 3.new 类名();
调用带参:1.类名 对象名(实参表);
2.new 类名(实参表);
//默认普通构造函数(无参构造函数)的定义
Hero::Hero()
{
cout << "调用了无参构造函数" << endl;
}
Hero::Hero(int hp)
{
cout << "调用了带参构造函数" << endl;
}
2.拷贝构造函数(复制构造函数)
1.没有返回值类型
2.函数名与类名相同
3.形参为const 类名& 对象名;
调用:1.用类的对象给另一个对象初始化时;
Hero hero1 = hero;//定义时赋值
2.显示调用拷贝构造函数;
Hero hero(hero1);
const Hero& other = hero1;
3.形参为类的对象时:将实参的值拷贝给形参
void test(Hero hero);
4.返回值为类的对象时
Hero::Hero(const Hero& other)
{
cout << “调用了拷贝构造函数” << endl;
}
当任意构造函数被显示给出时,
不能调用系统默认的无参构造函数,
也需要显示给出.
3.赋值函数:
类名& operator=(const 类名& other);
A& operator=(const A& other);
调用:用一个类的对象给另一个对象赋值时;
Hero hero;hero = hero1;
将hero1的成员的值一一拷贝给hero.
4.析构函数:
1.没有返回值类型
2.函数名:类名(A ~Hero)
3.没有形参
4.不支持重载
调用:内存被回收或者释放时.
对于系统回收的内存:先构造的后析构.
对于手动申请的内存:根据delete先后顺序.
5.类中数据成员为一个类的对象时;
6.没有内存泄漏的情况下,构造函数和析构函数的个数相同.
7.malloc/free和calloc/free不会调用构造函数和析构函数.
8.类的对象的内存:
1.内存对齐
2.空类:1个字节.
十二、类的封装
1.A类中具有B类的子对象,
构造函数:B-A
析构函数:A-B
2.默认拷贝构造函数:浅拷贝(将一个对象的值
一一拷贝给另一个对象)
浅拷贝可能会造成两个指针指向同一段内存.
当类的成员中含有指针并且指向堆区内存时,必须重写拷贝构造函数,
实现深拷贝.
Map::Map(const Map& other)
{
//m_row = other.m_row;
//m_line = other.m_line;
//pMap = other.pMap;
getMemory(other.m_row, other.m_line);
//将other.pMap指向内存中的值拷贝给this->pMap
for (int i = 0; i < m_row;i++)
{
memcpy(pMap[i], other.pMap[i], sizeof(int)*m_line);
}
}
3.数据成员赋值:
1.访问权限为public:对象.成员 = 值;
2.在类外访问私有成员,公有的成员方法:get/set函数
3.构造函数
4.成员初始化列表:(在分配内存时初始化)
Hero::Hero(int h, int l)
:hp(h),
lv(l)
{
/*hp = 100;
lv = 2;*/
getLv();
}
4.成员函数:
数据成员和成员函数:
const:增加程序的稳定性.
const数据成员:1.必须使用成员初始化列表初始化
2.不能修改该成员的值
math:PI
const的成员函数:
返回值类型 函数名(形参表) const;
1.不能在函数内修改成员的值.
2.形参的值可以修改.
3.函数内不能调用非const的成员函数.
5.static数据成员:
1.必须在类外初始化
类型 类名::变量名 = 值;//如果没有赋值,则默认为0
2.内存在全局区,程序结束内存才回收,
内存不属于类的一部分.
3.被类的所有对象共享,一个对象改变了其值,
其他对象访问时也是改变后的值.
4.1.静态成员可以通过类的对象调用
2.直接通过类名调用:A::a;
6.static成员函数:(在函数返回值前加上static)
函数内部只能访问static数据成员或者调用static成员函数.
管理者:只有1个.
设计模式:23种(大话设计模式)
单实例模式:只能创建一个对象.(程序结束才回收内存)
1.构造函数私有化
2.在类中写一个函数(静态)用来创建对象
3.保存第一次创建的实例的地址
(static成员函数只能访问static成员)
1.在类外不能创建对象:构造函数私有化.
2.在堆区创建对象:析构函数私有化
7.友元:实现数据共享(可以访问私有成员)
破坏了类的封装性.
1.友元函数:(相当于全局函数)
1.不属于类的成员函数,没有this指针.
2.声明:类中
friend 返回值类型 函数名(形参表);
3.定义:类中和类外都可以
不需要加类的作用域符号.
4.调用:函数名(实参表);不需要通过对象调用
2.友元类:
1.friend 类名;
1.不可逆性:A是B的友元类,B不一定是A的友元类.
2.不可传递性:A是B的友元类,B是C的友元类,A不一定是C的友元类.
3.不可继承性:A是B的友元类,C是A的子类,C不一定是B的友元类.
8.运算符重载:实现两个自定义类型对象之间的运算.
1.类的成员函数:
返回值类型 operator运算符(形参表);
函数名:operator运算符
形参表个数:运算符左右的式子个数-1
2.友元函数:
形参个数为式子左右个数.
一切能写成成员函数的重载都可以写成友元,只是形参个数+1.
能写成友元函数的重载不一定能写成成员函数,
比如运算符的左边的式子不为该类的对象.(必须使用友元)
cout<<hero;//运算符:<< 左边:ostream 右边;Hero
cout.operator<<(hero);
//hero<<cout;//hero.operator<<(cout);
9.指向类的数据成员
定义:成员的类型 类名::*指针名 = &类名::成员名;
在类外指针指向:只能指向公有成员
int Hero::*p = &Hero::hp;//p指针指向成员hp
Hero hero;
hero.hp = 100;
通过指针访问成员
hero.*p = 200;
cout << hero.hp << endl;
Hero hero1;
hero1.*p = 300;
cout << hero.hp << endl;
cout << hero1.hp << endl;
Hero* pHero = new Hero;
pHero->hp = 500;
pHero->*p = 600;
10.成员函数指针
返回值类型(类名::*成员函数指针名)();
赋值:1.&类名::成员函数名
调用:(对象.*成员函数指针名)(实参表);
(指向对象的指针->*成员函数指针名)(实参表);
指针指向Hero的getHp成员函数
int(Hero::*pFunc)() = &Hero::getHp;
cout<<(hero.*pFunc)()<<endl;//等同于hero.getHp();
(pHero->*pFunc)();//pHero->getHp();
十三、类的继承
1.继承:除了具有父类的属性和行为外,
还具有自己特定的属性和行为.
父类:桌子 子类:圆桌/方桌等
父类:动物 子类:猫/狗等
父类:物体 子类:英雄/怪物等
子类也是父类中的一种.
父类派生子类/子类继承父类.
定义:产生了新类.
class 类名:派生权限 父类名1,派生权限 父类名2…
{
访问权限:
//独有的数据成员和成员函数
};
成员访问权限
派生权限: public
protected
private
public
public
protected
不可访问
protected
protected
Protected 不可访问
private
private
private
不可访问
构造函数:父类构造函数(按照继承的顺序从左往右)-子类构造函数
析构函数:子类析构函数-父类析构函数(按照继承的顺序从右往左)
调用父类的带参构造函数:
成员初始化列表
(防止访问非法内存):
父类与子类对象之间的赋值:
子类对象可以给父类对象赋值,
父类对象不可以给子类对象赋值.
子类指针可以给父类指针赋值.
父类指针不可以给子类对象赋值.
内存:内存对齐,为父类成员+子类成员的大小.
String类的四大函数
十四、类的多态:
1.单实例模式/外观模式(Game)/简单工厂模式
2.隐藏:当子类中出现与父类同名的成员(二义性),
父类成员被隐藏.
同名方法不同形参时,子类对象也不能调用父类方法.
3.基类指针指向子类对象时,如果成员方法前
没有virtual关键字,则调用为基类函数.
4.指针调用成员或者非virtual成员函数时,
根据指针指向的类型.(静态多态(编译期多态))
多态:对同一消息的不同响应.
5.动态多态(运行时多态):继承+虚函数
(在函数声明前加virtual关键字)
静态多态:看指针类型
动态多态:看内存
6.具有虚函数的类的大小:+4个字节(虚指针内存)
虚表:按顺序存放了该类的所有虚函数的地址.
虚指针指向虚表.
虚函数:对象.虚函数(实参表);//通过虚指针找虚表中的虚函数
7.虚函数也可以被继承,
1.如果没有重写(override)虚函数,则父类与子类对象
虚指针指向的虚表中的内容相同.
2.如果重写:
1.virtual关键字可写可不写
2.函数形参表后override可写可不写,
写了表示重写父类的某函数
(如果父类没有该函数则会报错)
- 虚析构函数:基类指针指向子类对象,用基类指针释放对象,
如果基类不使用虚析构函数,不会钓鱼派生类析构函数,
需要在子类析构函数内释放内存时.
2.在子类中产生一个新的虚函数,
不会产生新的虚指针.而是原先的虚指针
指向虚表,虚表添加一个函数的地址.
3.哪些函数不能定义为虚函数(虚指针+this)?
1.构造函数(普通/带参/拷贝):没有为虚指针分配内存
2.静态函数:可以直接通过类名调用(也没有虚指针内存)
3.友元函数:不属于类的成员,没有this指针.
4.普通非成员函数:不属于类的成员.
5.内联函数:在编译阶段展开,虚函数是运行时多态.
6.纯虚函数:virtual 返回值类型 函数名(形参表) = 0;
抽象类:只有声明,没有定义(函数体)具有一个或以上纯虚函数的类称为抽象类.
Animal:
Object:
1.不能创建对象,可以创建指针或者引用.
2.抽象类的子类必须实现纯虚方法,才能
创建对象.
8.转换:
1.强转(不安全性)
2.四种安全类型转换
转换类型关键字<转换类型>(转换内容);
const_cast:
1.消除或者添加const属性
2.指针或者引用操作
static_cast:静态转换
1.普通类型之间的转换
2.指针间的转换(父子级关系,考虑偏移)
reinterpret_cast:重新解释转换
1.void到任意指针之间的转换
2.任意指针到void的转换
3.任意指针到任意指针之间的转换
dynamic_cast:动态转换
1.继承+虚函数