C++
一、C++基础
1.c++代码框架解析
#include <iostream>using namespace std;int main(){cout << "Hello world!" << endl;//把一个字符串插入到了输出流中return 0;}
iostream:包含了关于输入输出语句的函数
- input==(i)==:输入
- output==(o)==:输出
关于头文件的引入:
- 可以用原来c语言的语法:#include
可以用c++的语法:#include
- 去点==.h然后在前面加上c==就行
using namespace std:使用命名空间std(标准命名空间)
- 命名空间为了避免起名冲突,比如:葬爱-王者,葬爱-小雨
cout << "Hello world!" << endl:
cout:输出流<<:表示把hello World发送给cout打印endl:可以理解为回车换行(相当于\n)
endl和\n的区别:
\n:单纯回车换行
endl具有两重功能:
- 换行
fflush(stdin):清空缓存
- 刷新输出流:将缓冲区的数据全部传递到输出设备并将输出缓冲区清空。
二、变量、数据类型及运算符
变量:就是一个数据所占的内存空间
1.变量命名
- 为什么要变量命名:通过变量命名可以简单快速找到内存中存储的数据(在一定范围内)
- 如何避免重名:在C++中使用namespace命名空间技术
命名规则:
- 开头只能是字母或者下划线(不能以数字开头哦))
- 只能由==_、字母、数字==这三个元素组成
- 不能是关键字(保留字)
2.数据类型
数值
整型
- int(32bit)
- short(16bit)
- long和 l ong long
浮点型
- float
- double和long double(64bit)
非数值
string- char(字符型)8bit
size _ t == unsigned int
int main(){typedef char wode_char;//将char类型重命名为wode_char,就可以用wode_char来定义短整型啦wode_char name = 'hello world';cout << name << endl;return 0;}
3.使用变量的格式
- step1:声明变量
- step2:初始化变量(给变量第一次赋值)
//声明int score_totle;//初始化(赋值)score_totle = 10
注意
- 变量命名时名字不要重复
- 一条语句可以声明多个类型相同的变量
输出打印
#include <iostream>using namespace std;int main(){int salary = 2500;//打印月薪cout << “小明的月薪是:” << salary << endl;//解释:将salary插入到他前面的输入输出流里面一起输出}
#include <iostream>#include <cmath>using namespace std;int main(){//一直圆柱体的半径和高,求圆柱体的体积const float PI = 3.14;//定义了一个PI常量(常量是不能被改变的)float radius = 4.5;float height = 90.0f;double volume = PI * pow(redius, 2) * height;cout << "体积是: " << vloume << endl;return 0}
4.如何控制cout显示的精度
注意:C++默认展示6位有效数字
- step1.导入头文件
#include <iomanip> step1.让浮点数以小数的方式显示(不用科学计数 法来表示)
cout << fixed;
step3.控制显示的精度
cout << setprecision(2)- 精确到小数点后两位
- cout << fixed << setprecision(2)可以把两句代码连在一起写哦
5.cin
功能:C++中的标准输出对象,从标准输入
当我们从键盘输入字符串的时候,需要敲一下回车键才能够将这个字符串送入到缓冲区中,
那么敲入的这个回车键(\r)会被转换为一个换行符\n,
这个换行符\n也会被存储在cin的缓冲区中,并且被当成一个字符来计算!
举例:比如我们在键盘上敲下了123456这个字符串,然后敲一下回车键(\r),将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是7 ,而不是6。
#include <iostream>using namespace std;int main(){char a;int b;float c;stringcin>> a >> b >> c;cout << a << " " << b << " " << c << " " << endl;system("pause")return 0;}
6.字符型变量
char
- 字节位数:8位(bit)
- 空间:一个字节Byte
- 取值范围:-128 ~ +127
输入字符时建议使用:
cin.get()函数
1.setw()函数:设置宽度
int main(){double a1 = 123;double a2 = 234;double a3 = 345;cout << left;//对齐方式设置为左对齐,默认为右对齐cout << setfill('-')//设置空白处的填充为'-',默认填充为空格cout << setw(8) << a1 <<setw(8) << a2 <<setw(8) << a3 << endl;}
2.i++和—i
int main(){int num1 = -5, num2 = 2;num1 = num2++ - --num1;/*运算流程①--num2=> num2 = num2 - 1]此时连个num2的值都是1,num2 - num2 = 0 == num1②计算num2++num2 = num2 + 1num2进行了加一减一操作,最后结果还是2*/cout << num1 << '\t' << num2 << endl;}
三、表达式和条件结构
表达式:符号与操作数的组合
1.赋值运算符
①单等号 =====
计算顺序:从右往左
i += 1
i = i + 1
i = 1
i = i 1
2.关系运算符
>、<、==
>=、<=、!=
int main(){cout << boolalpha;//输出的时候True不会输出1而会输出Truecout << "15 > 35吗?" << (15 > 35) << endl;cout << "15 < 25吗?" << (15 < 35) << endl;return 0;}
3.逻辑运算符
| 运算符 | 表达式 | 说明 |
|---|---|---|
| && 并且 | 条件1 && 条件2 | 只有两个条件都是真时最后的结果才是真 |
| ||或 | 条件1 || 条件2 | 两个条件只要有一个条件成立就成立 |
| !非 | ! 条件 | 就是取反的意思 真变成假 假变成真 |
4.位运算符
- 将数字转化为2进制,然后再进行运算

负数二进制转化为10进制的方法
- 将负数二进制取反
- 取反后加一
- 转化为10进制
- 加上负号
举例
11111101- 取反:00000010
- 加一:00000011
- 转换:3
- 加负号:-3
5.sizeof运算符
- 获得数据类型所占用的内存空间的大小
- 结果以字节为单位==(1Byte == 8bit)==
运算符优先级:
- 单目运算符 > 算数运算符 > 逻辑运算符 > 赋值运算符
三元运算符
int num = 5 > 6 ? 10 : 12;//判断如果5>6为真就会返回10//如果5>6为假,就会返回12
6.条件结构
if(条件为真){//代码块1}else{//代码块2}
int main(){//使用程序判断用户输入的字符是否是合法的硬盘盘符char pan = '\0'//默认设置为空字符cout << "请输入一个字符,我来判断是否合法";cin >> pan;//pan 的范围在'A'~'Z'之间if(pan >= 'A' && pan <= 'Z'){cout << "这是合法的盘符" << endl;}else{cout << "这是不合法的盘符,请重新输入" << endl;}}
- 初学时,能定义多少变量,就定义多少变量
7.多重if结构
int main(){double flowerPrice;//花的单价//打印剧情cout <<"黎明前的黑暗渐渐褪去,海天之间透露着一抹亮光,像是点燃的火把......" << endl;cout << "小男生给小女生送花,小女生问:这花多钱吖" << endl;cout << "小男生:";cin >> flowerPrice;if(flowPrice >= 5000){cout << "直接去领证" << endl;}else if(flowPrice <5000 && flowPrice >=3000){cout << "今天去heiheihei" << endl;}else{cout << "拜拜" << endl;}return 0;}
8.switch结构
switch(表达式){case 常量1:语句1;break;case 常量2:语句1;break;......default:语句;}
switch:swithch后的表达式只能是整型或字符型(不能是变量)
case:
- case后的表达式的值不能相同
- case后允许多条语句,不需要大括号
- 如果不添加break语句,需要特别注意执行顺序
default:
- case和default句子的先后顺序可以自行变动
- default可以省略
break:
- 跳出,退出这个结构
- 如果有多个case语句,中间没有break语句,那么就会依次挨着执行这些case语句
int main(){int choice;cin >> choice;switch(choice){case 1:cout << "我选择老师" << endl;case 2:cout << "我选择程序员" << endl;case 3:cout << "12345" << endl;default:cout << "666" << endl;}
四、while和do…while
1.while循
控制循环的次数
循环中,通过控制变量来控制循环的次数
循环三要素
循环变量的初值
- 在循环外自定义一个变量作为循环变量初值
循环变量的判断
- while括号里面左判断,如果成立(返回True),循环继续
循环变量的更新
- 每次循环让循环变量改变(可能是自加,也可能是自减或者别的)
while循环特点:先判断,再执行
int main{int i = 1;//循环变量的初值while(i <= 10){//循环变量的判断cout << "小人本住在苏州城边..\t" << i << "遍\n";i ++;//循环变量的更新}return 0;}/*while(循环条件){循环操作语句}*/
2.while循环练习题
使用循环计算1-100的累加
- 需要循环变量
- 需要累加变量
int main(){int a = 0;//定于循环变量int sum = 0;//定于总量while(a < 100){sum = sum + a;a ++;cout << a <<endl;}//输出cout << sum << endl;return 0;}
- 使用循环实现三次密码输入错误退出系统
#include <cstdlib>using namespace std;int main(){//先设置密码string passward;//密码cout << "请输入密码:" << endl;cin >> passward;int i = 0;//循环变量控制循环次数while(i < 3){cout << "请输入密码:" << endl;cin >> passward;i ++;if(passward != "12345678" and i ==3){cout << "3次输入次数已经到了,系统强制退出"<< endl;}i ++;}return 0;}
- 某宝双十一2015年交易金额为800亿,每年递增25%
- 问:那一年交易额达到2000亿?
int main(){//总额初始为800double sum = 800;//增幅double add = 1.25;//定义循环变量控制循环次数int i = 2015;while(sum < 2000){sum = sum * add;i ++;}cout << "到"<< i <<"年的时候营业额达到了"<< sum << "亿元" << endl;return 0;}
3.codeblock调试C程序
调试流程:
- 分析错误
- 设置断点
- 启动调试
- 单步运行
- 观察变量
- 发现问题
- 修正代码重新运行
4.do-while循环
do{循环操作}while(循环条件);
特点:先执行,再判断
- 先执行一次循环操作==(无论如何循环体都会执行一次)==
- 再判断:如果符合循环条件,继续执行循环操作
- 不符合循环条件:退出
五、for循环
for(表达式1;表达式2;表达式3){循环语句;}//代码举例子const int N = 20;//常量for(int i = 0;i < N;i ++){cout << "for循环也不难" << endl;}//上面的代码和下面这个代码等价const int N = 20;while(i < N){cout << "for循环也不难" << endl;i ++;}
三个表达式:
- ==表达式1:==通常是给循环变量赋初值,可以省略(例如:i = 0)
- ==表达式2:==循环条件,是否继续执行循环,可以省略 (例如:i < 10)
- ==表达式3:==更新循环变量的值,可以省略 (例如:i ++)
计算平均数:
int main(){//输入六个数字,计算这六个数字的平均值double num = 0;//初始化要输入的数字double sum = 0;double average = 0;for(int i = 0;i < 6;i ++){cout << "请输入第" << i + 1 << "月的工资";cin >> num;sum += num;}//计算平均数average = sum / 6;cout << "平均工资为:" << average << endl;cout << "工资总数为:" << sum << endl;return 0;}
使用循环打印1997年7月的月历
- 已知:1997年7月1日(星期二),香港回归
int main(){//定义变量int day = 31;//7月共有31天int dayOfWeek = 2;//7月第一天是周二cout << "一\t二\t三\t四\t五\t六\t日" << endl;//先打印七月的第一天for(int i = 0; i < dayOfWeek - 1; i ++){cout << '\t';//不写endl}for(int i = 1;i <= 31 ;i ++){cout << i;if((i + dayOfWeek - 1) % 7 == 0){cout << '\n';//不写endl}else{cout << '\t';//不写endl}}//在最后再写endl//endl的刷新作用:将两个endl之间(缓冲区)的所有内容全部展示到显示器上,并清空缓冲区cout << endl;return 0;}
1.break语句
break语句作用:跳出本层循环(不是本次循环哦),执行循环以后的语句
int main(){for(int i = 0;i < 10; i++){cout << "i的值:"<< i << '\n';for(int n = 10; n >= 10 && n <20; n++){cout << "n的值:" << n <<'\n';if(n == 15){break;}}}cout << endl;return 0;}
2.continue语句
continue语句的作用:跳过本次循环,继续下次循环
3.循环结构总结
- 外层循环控制行数==(行数,换行)==
- 内层循环控制列数==(列数,列的图形)==
for(int i = 0; i < 10; i++){for(int j = 0; j < 5; j++){//控制列数,且控制列的图形cout << "※"}//控制行数cout << endl;}
- 打印三角形:讨论
i和j的关系
int main(){//打印三角形的上半部分for(int i = 0; j < 4;i++){for(int j =0; j <= 2 -i; j++){cout << " ";}for(int j = 0;j <= 2*i; j++){//cout << (char)('A' + i)//用上面这个代码可以打印出ABCD/**ABBBCCCCCDDDDDDDEEEEEFFFG**/cout << "*";}cout<< endl;}//打印三角形的下半部分for(int i = 0; j < 4;i++){for(int j =0; j <= i; j++){cout << " ";}for(int j = 0;j <= 4 - 2*i; j++){cout << "*";}cout<< endl;}return 0;}
六、数组及常用算法
数组结构和基本要素
- 标识符:数组名称,用于区分不同数组
- 数组元素:向数组中存放的数据
- 元素下标:向数组中存放数据
- 元素下标:对数组元素进行编号
- 元素类型:数组元素的数据类型
数组特点
- 数组只有一个标识符(数组名)
- 下标从0开始
- 每个元素都通过下标来访问
- 数组长度固定不变,避免数组越界
1.几种常见定义数组的方式
int num[] = {123,23,45,'c'}//'c'是字符类型(char),在c和c++中用整型来表示//定义数组的语法//数组类型 数组名[数组大小]//这里数组大小指的是:数组内元素的个数//数组大小可以是变量int nums[25];//1.后面元素个数与声明的个数一样(合法)int years[6] = {1,2,3,4,5,6}//2.后面元素的个数小于声明的个数(合法,注意,不能大于声明的个数)int month[12] = {1,2,3}//3.不声明元素个数(计算机会自动计算数组的大小)int days[] = {1,15}//4.省略等于号也是可以的int days[]{1,2}//5.大括号里面可以位空,但是声明元素个数不能为空,代表这个列表里的所有元素都为空int days[100]{}//5.这样不行int days[] = {}
2.一维数组的动态赋值
//动态地从键盘录入信息并复制const int N = 5;int nums[N];//数组长度for(int i = 0; i < N; i++){cout <<"请输入第"<< (i+1) <<"个数组元素";cin >> nums[i];}for(int i = 0; i < N; i++){cout << nums[i] << endl;}cout << "数组的大小:" << sizeof(nums) /sizeof(int) <<endl;//数组中元素个数的求法//这种方法对string类型无效cout << "数组的大小" << sizeof(数组名) / sizeof(数组元素的数据类型) << endl;
3.一个小练习
有一个数列:8,4,2,1,23,344,12
- 循环输出数列的值
- 求数列中所有数值的和及平均值
int main(){//c++11语法中可以不用=int nums[]{8,4,2,1,23,344,12};int numsLen = sizeof(nums) / sizeof(ing);//计算出数组的长度for(int i = 0; i < numsLen; i++){cout << nums[i] <<'\t';}cout << endl;//累加操作for(int i = 0; i < numslen; i++){sum += nums[i];}//'\t'为制表符cout << "数列的和为:" << sum << "\t平均值为:" << sum / numsLen <<endl;}
4.数组的应用实例
- 求数组中的最大值和最小值
- 定义一个整型数组,复制后求出奇书个数和偶数个数
int main(){int nums[] = {8,4,2,1,23,344,12}int numLen = sizeof(nums)/sizeof(int);int jishu=0,oushu=0,i=0;while(i<numlen){if(nums[i]%2==1){i++;jishu++;}else{i++;oushu++;}}cout << "奇数个数为:" << jishu << endl;cout << "偶数个数为:" << oushu << endl;}
- 查找输入的数字在数组中的下标,没有找到泽下标为-1
int main(){int nums[] = {8,4,2,1,23,344,12}int numLen = sizeof(nums)/sizeof(int);int searchNum; //用户要查找的数字int searchIndex = -1; //数字的下标cout << "请输入要查找的数字";cin >> searchNum;//使用循环查找for(int i = 0; i < numsLenl; i++){if(nums[i] == searchNum){searchIndex = i;//记录下来下标break;}}if(searchIndex == -1){cout<<"数组中没有这个数字"<<endl;}else{cout << searchNum<<"在数组中的下标为"<<searchIndex<<endl;}}
5.数组排序
5.1冒泡排序
- 第一轮比较的次数:数组长度-1
- 下一轮比上一轮比较的次数:少一次
- 每一趟确定一个最小的数,并将这个最小的数放在列表最后
下一趟比较的次数比上一趟比较的次数少一次
- 因为已经确定了最小的数了,刚才确定过的数就不用再次比较了
//使用冒泡排序,将列表由大到小排列int temp;//设置临时变量int nums[] = {12,45,23,56,7,43,2};//外层循环控制每轮的比较的交换for(int i = 0; i < numLen; i++){for(int j = 0; j<numLen-i; j++){if(nums[j]<nums[j+1]){temp = nums[j];nums[j] = nums[j+1];nums[j+1]=temp;}}}for(int i = 0;i<numLen;i++){cout<<nums[i]<<endl;}
5.2选择排序
- step1:从数组中选出最小的数据,将其和第一个位置的数据交换
- setp2:接着从剩下的n-1个数据中选择次小的1个元素,与第二个元素进行交换
- step3:不断重复,知道穷尽列表
int main(){//选择排序int nums[] {8, 4, 2, 1, 23, 23, 344, 12};int numLen = sizeof(nums)/sizeof(int);//擂台变量int min = nums[0];//假设最小值是数组的第一个元素int minIndex = 0;//最小值的初始下标设置为0int temp;//临时变量用来交换for(int i = 0; i < numsLen; i++){min = nums[i];//假设第i个元素是最小值了minIndex = i;for(int j = i + 1;j < numsLen;j++){//打擂台if(nums[j] < min){min = nums[j];minIndex = j;}}//交换if(minIndex > i){temp = nums[minIndex];nums[minIndex] = nums[i];nums[i] = temp;}}cout << "排序后:" << endl;}
6.数组元素的删除和插入
数组大小一旦确定,就不能再更改啦!
int main(){//对四个数字进行排序double power[99];int powerCount = 0;//这是当前数组中的元素个数double insPower;//要插入的攻击力数值int insertIndex = 0;//默认插入到第一个元素的位置power[powerCount++]=45771;//++放在后面,先执行复制,再执行自加运算power[powerCount++]=42322;power[powerCount++]=40907;power[powerCount++]=40706;//用冒泡排序进行排序double temp;for(int i = 0; i < powerCount; i++){for(int j = 0; j < powerCount - i - 1++){if(power[j] < power[j+1]){temp = power[j];power[j] = power[j+1];power[j+1] = temp;}}}//插入元素cout << "请输入要插入的数字:";cin >> insPoser;//插入以后还要保证其是有顺序的/*插入三步走:1,找到第一个比插入数字大的位置insertIndex(先找到要插入的位置)2,将后面的元素依次往后移动一个位置(给要插入的元素空出来一个位置)3,将要插入的数字赋值给下标为insertIndex的元素(元素赋值)**/insertIndex = powerCount;//step1:找到第一个比插入数字大的位置insertIndex(先找到要插入的位置)for(int i = 0; i < powerCount; i++){if(insertPower > power[i]){insertIndex = i;break;}}//step2:将后面的元素依次往后移动一个位置(给要插入的元素空出来一个位置)for(int i = powerCount - 1; i >= insertIndex; i--){power[i+1] = power[i]}//step3:将要插入的数字赋值给下标为insertIndex的元素(元素赋值)power[insertIndex] = insertPower;powerCount++;/*删除三步走:1.找到要删除的元素下标2.把下标后面的元素都往前移动一个单位3.总长度-1*///step1:找到要删除的元素下标for(int i = 0; i < powerCount;i++){if(insertPower>power[i]){insertIndex = i;break;}}//step2:把下标后面的元素都往前移动一个单位if(deleteIndex == INT_MIN){cout << "没有找到要删除的元素,删除失败!"<< endl;}else{for(int i = deleteIndex; i < powerCount;i++){power[i] = power[i+1];}}//step3:总长度-1powerCount--;}
7.二维数组
int main(){//使用二维数组string stu_names[] {"张三","李四","王五"};string course_names[] {"语文","数学","英语"};//定义二维数组的宽和高const int ROW = 3;const int COL = 3;//定义二维数组double scores[ROW][COL];for(int i = 0;i < ROW; i++){for(int j = 0;j < COL;j++){cout << stu_names[i] <<"的"<<course_names[j]<<"成绩是:";cin >> score[i][j]}}//打印结果//step1:先打印第一排的科目(最前面要空出来一格)cout << "\t";for(int i = 0;i<COL;i++){cout<<course_names[i]<<"\t";}cout<<endl;//step2:打印后面的内容,成绩+姓名for(int i = o;i<ROW;i++){//外层循环控制列//内层循环控制行cout<<stu_name[i]<<"\t";for(int j = 0;j<COL;j++){cout << scores[i][j]<<"\t";}cout<<endl;}}
8.数组的替代品
8.1 向量容器vector
- 动态数组,可以在运行阶段设置长度
- 具有数组的快速索引方式
- 可以插入和删除元素
8.2 定义和初始化
#include <vector>vector<double> vec1;//常用vector<string> vec2(5);vector<数据类型> 名称(共有几个元素, 一个元素占多大空间);vector<int> vec3(20,998);//名叫vec3的容器,分配20个元素,每个元素占空间大小为998Byte
常用的函数

#include <vector>#include <>int main(){vector<bouble> vecDouble {98.7, 23.5,34.6,34.6};//向容器(数组)中插入数字vecDFouble.push_back(100.8);//在容器尾部插入一个数字//集合的通用便利方法:使用迭代器 iterator//迭代器的基本用法vector<double>::iterator it;//得到迭代器对象//it.begin从第一个元素开始迭代for(it = vecDouble.begin();it != vecDouble.end(); ++it){cout << *it << endl;}//排序sort(vecDouble.bergin(),vecDouble.end())}
七、指针基础
指针简介:指针是一个值为内存地址的变量(或数据对象)。
简言之,指针和int,double等的变量类似,也是一个变量,但是指针里面保存的内容是内存地址。

当ptr_year保存year的地址的时候,我们就收ptr_year指向了year
1.基本用法
数据类型 * 指针变量名;
int * ptr_num;char * ptr_num;float * money_ptr;double * p_price;int num = 10;ptr_num = #
注意:
int* p的写法偏向于地址,即p就是一个地址变量,表示一个十六进制地址
int p的写法偏向于值。
p是一个整型变量,能够表示一个整型值(建议两者相结合进行理解)声明中的
*h和使用中的*的含义完全不一样- 在声明语句中,表示指针类型
- 非声明语句中,*表示取值,取这个指针变量中保存的地址所对应的值
2.使用实例
间接运算符:*
int num = 1023;int * ptr_num;ptr_num = #*ptr_num = 1111;//*在非定义语句中代表取值,取这个指针变量中保存的地址所对应的值
int mian(){double num = 1024.5;//声明一个指针,指向num变量double * ptr_num = #cout << "ptr_num的值" << ptr_num << endl;cout << "ptr_num指向空间的值是:" << *ptr_num << endl;return 0;}
3.常见指针类型
3.1 空指针()
定义:空指针不指向任何对象,在试图使用一个指针之前可以首先检查是否为空
用法:
int * ptr1 = nullptr;//等价于int * ptr1 = 0;int * ptr2 = 0;//直接将ptr2初始化为字面常量0#include cstdlibint * ptr3 = NULL;//等价于int * ptr3 = 0;
注意:出现指针必须要初始化,不要出现没有赋值的指针(野指针),尽量等定义了对象之后再定义指向它的指针
或者先给指针一个地址(空地址也行),再在语句中使用指针。
3.2 void *指针
一种个数 的指针类型,可以存放任意对象的地址
注意:
- void *指针存放一个内存地址,地址指向的内容是什么类型不能确定
void*类型指针一般用来:
- 拿和别的指针比较
- 作为函数的输入和输出
- 赋值给另一个void *指针
若指针已申明为指向某种类型数据的地址,则它不能用于存储其他类型数据的地址
4.引用
引用出现的目的:指针的书写看起来不好看,代码不美观,使用引用让代码看起来更加清爽美观
啥意思:为对象起了另个一名字(引用即别名)
看个例子:
int int_value = 1024;//refValue指向int_value,是int_value的另一个名字int& refValue = int_value;//注意:引用必须被初始化(引用的底层也是通过指针实现的)
注意:
- 引用并非对象,只是为一个已经存在的对象起别名
- 引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起
- 引用必须初始化,所以使用引用之前不需要测试其有效性,因此使用引用可能会比使用指针效率高
- 引用不能直接引用另个一常量
引用和指针的关系
- 引用时对指针进行了简单的封装,底层仍然是指针
- 获取引用地址时,编译器会进行内部 转换
5.指针与数组
数组:
- 存储在一块连续的内存空间中
- 数组名就是这块连续内容空间的首地址
- 或者说数组名中存放的是数组首个元素的地址
- 数组数组本质上是:
数据类型[ ]这样一个类型
//指针和数组区别int main(){double score[] {11, 22, 33, 44, 55};double * ptr_score = score;cout << score <<sizeof(score) << '\t' << sizeof(ptr_score) << endl;//输出结果:40 4//数组的大小是40个Byte,一共有5个元素,一个元素8个Byte//指针的大小是4个Byte,地址的大小就是4Byte(32bit)}
6.指针运算
6.1 指针的算数运算
- 指针的递增和递减(++,减减)
- 一般理解为指针的平移
- 一个类型为T的指针的移动,以sizeof(T)为移动单位
八、动态分配内存
使用new分配内存
- 指针的真正用武之地:在运行阶段分配未命名的内存以存储值
- 在这种情况下,只能通过指针来访访问内存
使用
delete释放内存==(类似于c中的free()函数)==- 必须与new配对使用
- 不要释放已经释放的内存
- 不要释放普通变量的内存
//1.在运行阶段为一个int值分配唯未命名的内存//2.使用指针(ptr_int)来访问(指向)这个新的内存空间(右->左)int * ptr_int = new int;delete ptr_int;//释放由new分配的内存
编译时:(包办婚姻)int num[56];
运行时:int * nums = new int[5]
warnning:
1.不要创建两个指向同一块内存的指针,有可能误删除两次
1.动态分配数组
使用
new创建动态分配的数组- int * intArrary = new int[10];
- new运算符返回第一个元素的地址
使用delete[]释放内存
- delete [] intArray;
- 释放整个数组
注意:
- 不要使用delete释放不是new分配的内存
- 不要使用delete释放同一个内存两次
- 如果使用new[]为数组分配内存,则对应delete[]释放内存
2.补充:程序的内存分配
2.1 栈区(stack)
- 由编译器自动分配释放,一般存放函数的参数值、局部变量等
- 操作方式类似数据结构中的栈,先进后出
- 变量名、函数名、指针名等都在栈区
2.2 堆区(heap)
- 一般由程序员分配释放,若程序不是放,程序结束时可能由操作系统回收
- 注意:与数据结构中的堆是两回事,分配方式类似链表
2.3 全局区(静态区static)
- 全局变量和静态变量存储在一起
- 程序结束后由系统释放
2.4 文字常量区
- 常量字符串就放在这里,程序结束由系统释放
2.5 程序代码区
- 存放函数题的二进制代码
int num1 = 0;//全局初始化int * ptr1; //全局未初始化int main(){//栈区int num2;//栈区char str[] = "栋哥";//栈区char * ptr;//“栋哥”以及\0在常量区,ptr3在栈区char * ptr3 = "栋哥";//全局(静态)初始化static int num3 = 1024;//分配内存在堆区ptr1 = new int[10];ptr2 = new char[20];//注意:ptr1和ptr2本身在栈区中}
2.6 完成数组的逆序
int arrays[] {15, 23, 30, 40, 50,23};int * ptr_start = arrays;//指向第一个元素//数组名中存放数组首个元素的地址,所以可以直接赋值给指针变量int * ptr_end = arrays + 5;//指向最后一个元素int temp;//偶数也可以正常运行while(ptr_star != ptr_end && ptr_end > ptr_star){temp = *ptr_star;*ptr_star = *ptr_end;*ptr_end = temp;ptr_start++;ptr_end--;}temp = *ptr_star;*ptr_star = *ptr_end;*ptr_end = temp;for(int i = 0; i < 6; i++){cout << arrays[i] << endl;}
九、函数
1.函数回顾
1.1 函数分类
内置函数
- STL标准模板库
- Boost C++:也是一个C++的库
- 自定义函数
1.2 函数“三要素”
- 返回值类型
- 函数名
- 参数列表
2 书写自定义函数
int sum(int, int);//函数原形int main(){//函数调用int result = sum(5, 3);}//函数定义int sum(int num1, int num2){//函数实现的代码}
一个小例子
using namespace std;//计算两个数字之和的函数int sum(int, int); //函数原形int main(){//调用函数int reuslt = sum(5, 6)cout << "结果为:" << result << endl;return 0;}//int是返回值类型int sum(int num1, int num2){//1.计算两个数字之和int reusult = num1 + num2//2.返回计算的结果return result;
注意:
- 函数原形与函数定义的头部类似,最后以分号结尾
函数原形中的参数名称可以省略,只写参数类型就行
- int sum(int, double)
- c++中返回值类型不能是数组,但是可以是其他任何类型(可以将数组作为结构或对象组成部分返回)
/*三种形状的体积计算公式如下:长方体:长 × 宽 × 高圆柱体:圆周率 × 半径的平方 × 高圆锥体:1/3 × 底面积 × 高*/#include <iostream>#include <iomanip>#include <windows.h>#include <cstdlib>#include <cmath>using namespace std;void calcCuboid();//计算长方体的体积void calcCylinder();//计算圆柱体的体积int main(){//长方体的数据//cout << "请输入长方体的数据:";//cin >>"长:">> c1>>"宽">>k1>>"高">>g1;int choice = -1;while(choice){cout << "1、长方体" << endl;cout << "2、圆柱体" << endl;cout << "0、退出" <<endl;cin >> choice;switch(choice){case 1:calcCuboid();break;case 2:calcCylinder();break;}}cout << "感谢您的使用"<<endl;return 0;}void calcCuboid(){//输入长宽高double len, width, height;cout << "请输入长宽高:";cin >> len >>width >> height;//计算体积double v = len * width * height;cout << "长方体的体积是"<<v<<endl;}void calcCylinder(){//半径和高double r, height;cout << "请输入半径和高:";cin >> r >> height;//计算体积double v = 3.14 * pow(r, 2) * height;cout << "长方体的体积是"<<v<<endl;}
3 参数和按值传递
- 按值传递
给函数传递参数(变元)时,参数(变元)值不会直传递给函数,而是先制作参数(变元)的副本,存储在栈上,再使这个副本可用于函数,而不是使用初始值
//这个函数运行的结果仍然是10,即,在main()函数中的变量num的值并没有被改变//因为第九行num在给change()函数传递参数的时候,仅仅把num中10的值复制了一份传递给了change()函数中的变量numvoid change(int num){num++;}int main(){int num = 10;change(num);cout << num << endl;return 0;}//下面这个程序输出的结果是11/*因为change()函数中传递的参数为变量的引用(引用底层由指针实现,其实传递的就是变量的指针)所以,main()函数中的变量num的引用被传入到change()函数中以后,其值就会被改变*/void change(int &num){num++;}int main(){int num = 10;change(num);cout << num << endl;return 0;}
4 使用数组作为函数的参数
注意:
- 数组作为函数实参时,只传递数组的地址==(首地址)==,并不传递整个数组的空间
- 当用数组名作为实参调用函数时,数组首地址指针就被传递到函数中
5 函数指针
函数也有地址:
- 函数的地址是存储其机器语言代码的内存开始地址
- 好处:可以在不同的时间使用不同的函数
//函数指针的声明//函数原形double sum(double, double);//函数指针声明double (*ptrSum)(double, double)
注意:
该语句声明了一个指针ptrSum,指向一个函数
double ptrSum(double, double)
不是函数指针,而是
声明了一个函数ptrSum,返回double 类型
int power(int, int);int main(){//根据int power(int, int)声明函数指针//声明函数指针, 函数的指针名叫ptrPower, 相当于定义普通指针变量时候的int * ptrIntint (*ptrPower)(int, int);//声明的函数指针指向函数,以便调用//因为函数名就是函数的首地址,所以可以直接给指针赋值ptrPower = power;cout << ptrpower(3, 4) << endl;}int power(int a, int b){s = power(a, b);return s;}
使用函数指针注意事项:
- 先定义函数(普通函数)
- 然后声明函数指针
- 把定义的函数名(原函数的地址)赋值个函数指针
十、函数进阶(C++的特性)
1.内联(inline)函数
- 是c++为了提高程序运行速度所作的一向改进
与常规函数的区别在于被调用时的运行机制不同
- 在调用内联函数时,将函数体内的代码,直接赋值一份到调用代码的地方
啥时候使用内联函数?
- 代码执行时间很短的时候,内联调用就可以节省大部分时间
include <iostream>//内敛函数可以在函数声明时候加关键字inline int pow(int, int);int main(){int resutlt = pow(5, 3);}//内联函数也可以在定义函数时加关键字inlineinline int pow(int num1, int num2){int result = 1;for(int i = 0; i < num22; i++){result *= num1;}return result;}
内联函数的前世今生
#define s(num) num * num//宏定义了一个S(num)函数//以后在所有使用S(num)的地方,就自然被替换成num * num
2.引用回顾
引用:为对象起另一个名字(引用即别名)
- 引用必须进行初始化
引用不能直接引用常量(字面量)
- 除非,变量也是一个常量
int int_value = 1024;//refValue指向value,是int_value的另一个名字int& refValue = int_value;/**注意:1.避免还没有初始化就引用2.不能直接引用常量,除非对变量使用const*/const int & refValue2;
注意:
1.引用不是对象,只是为一个已经存在的对象起的别名
2.引用更紧接const指针,一旦与某个变量关联起来,就将一直效忠于它
3.将引用变量作为参数时,函数将使用原始数据,而非副本**
4.当数据所占内存比较大时,建议使用引用参数
3.使用引用参数
使用引用的理由:
- 可以更加简便的书写代码
- 可以直接传递某个对象,而不仅仅只是把对象复制一份
- 不想让函数修改传入的参数本身时,使用
const函数
一个小例子:
#include <iostream>#include <iomanip>#include <windows.h>#include <cstdlib>#include <cmath>using namespace std;//Swap1直接传入参数void Swap1(int, int);//Swap2通过指针传入参数void Swap2(int*, int*);//Swap3通过引用传入参数void Swap3(int&, int&);//show展示函数,不希望在函数里改变变量的值void show(int&, int&, char);int main(){int num1 = 10, num2 = 5;Swap1(num1, num2);show(num1, num2, '1');Swap2(&num1, &num2);show(num1, num2, '2');Swap3(num1, num2);show(num1, num2, '3');return 0;}void Swap1(int num1, int num2){int temp = num1;num1 = num2;num2 = temp;}void Swap2(int* num1, int* num2){int temp;temp = *num1;*num1 = *num2;*num2 = temp;}void Swap3(int& num1, int& num2){int temp;temp = num1;num1 = num2;num2 = temp;}void show(int& num1, int& num2, char name){cout << "执行"<< name <<"交换后num1:" << num1 << "\tnum2:"<< num2 <<endl;}
使用引用的理由:
- 可以更加简便地书写代码
- 可以直接传递某个对象,而不只是把对象赋值一份
4.函数返回引用类型
不要返回局部变量的引用
- 变量的生存周期
```c int& sum() { int num = 10; //注:rNum是在sum()函数中定义的,所以叫做局部变量 //rNum的生存周期只在sum()函数中! int& rNum = num; return rNum;//返回一个局部变量
/*函数中的局部变量会被【内存回收】:
内存回收并不是把内存保存的内容设置为0,而是指内存中变量申请的这个内存已经分配给了别人。*/
}
void test(){ int x = 1; int y = 2; int z = 1024; }
int main(){ //result在这里引用了一个局部变量 int& result = sum(); test() cout << “result = “ << result << endl; return 0; }
-函数可以不返回值,**默认返回最后一个更新的引用对象(参数)本身**-```cint& sum(int& num1, int& num2){num1++;num2++;num1++''//没有返回值,运算结果返回了最后一个跟新的引用//一定要记得写返回值return num1 + num2;//肯定报错}int main(){int num1 = 10, num2 = 15;int& result = sum(num1, num2);cout << "result = "<< result <<endl;return 0;}
- 返回引用时,要求函数必须包含被返回的引用对象
5.引用参数小结
使用引用参数的一些指导原则
- 能够修改调用函数中的数据对象
数据对象较大市,传递引用可以提高程序的运行效率
函数中不需要修改传递的参数
- 如果数据对象很小,就可以按指传递,不用引用
- 传递数组只能使用指针,并使用const关键字
- 较大的对象则使用const指针或应用,这样可以避免传递非常大的副本,提高程序运行效率
函数中需要修改传递的参数
- 数据对象是基本类型或结构时,可以使用指针或者引用(基本类型建议使用指针)
- 数据对象是数组时只能使用指针
- 数据对象时类对象时,要求必须使用引用
13.8 聊天小程序
int main(){}
6.默认参数
使用默认参数
- ```c void sample(int = 10);//声明的时候把参数的默认值加上 int main(){ sample();//有默认参数不传入参数也不会报错 sample(12); }
//定义的时候就不用写啦 void sample(int num) { cout << num << endl; }
-注意:-默认参数可以在函数原型或者定义中给出,不能在这两个位置同时出现-对于带参数列表的函数,必须要求从右向左添加默认值-```cvoid test1(int a, int b = 5, int c = 10);//正确void test2(int a, int b = 5, int c);//错误void test2(int a = 1, int b = 5, int c = 2);//正确
7.函数重载
函数重载
指可以有多个同名的函数
函数名相同,参数列表不同(特征标不同)
特征标:重载-编译器在编译时,根据参数列表对函数进行重命名
- ```c void Swap(int a, int b); //编译器编译时:Swap_int_int
void Swap(float a, float b); //编译器编译时:Swap_float_float(重载)
//重载决议
-编译器把 引用和类型本身视为同一个特征标-调用匹配函数时,不区分const和非const变量(使不使用const都一样)<a name="886aa3ac"></a>#### 使用函数重载实现对不同数据类型的数组进行排序```cint iNums[] = {56, 54, 12, 89, 43};float fNums[] = {78.0f, 5.7f, 42.8f, 99.1f};double dNums[] = {78.9, 23.6, 77.8, 98.5, 33.3}
8.函数模板
所谓函数模板,就是建立一个通用函数
- 函数定义时,不指定具体的数据类型(使用虚拟类型替代)
- 函数被调用时编译器根据实参反推数据类型-类型的参数化
//函数声明template<typename T> void Swap(T&, T&);//typename后面的T是一个虚拟的数据类型/***使用模板技术实现变量交换值*/template<typename T> //模板头void Swap(T &a, T &b){T temp = a;a = b;b = temp;}
十一、类与对象
1.面向对象编程
所谓面向对象,就是基于对象概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界和设计、构建响应的软件系统(模拟现实)
- 对象是由==数据(属性)和容许的操作(方法)==组成的封装体,与客观世界有直接对应关系
- 面向对象不是某一种语言的特性,而是一种编程思想
注意:
- 采用过程性编程方法时,首先考虑要遵循的步骤,然后考虑如何表示这些数据
- OOP程序员(面向对象)首先会考虑数据,不仅要考虑如何表示这些数据,还要考虑如何使用数据
2.类和对象
2.1 抽象
- 从具体事物抽取共同本质特征
C++中的类
- 类是一种将抽象转换为用户定义类型的工具
- 将数据表示(属性、成员变量)和操纵数据的方法(方法、成员函数)组合成一个整体
- 类的实例称为对象(类是抽象的,实例是具体的)
- 类中的变量和函数称为成员
2.2 类的声明
使用class/struct关键字声明类型
class 类名{};class LandOwner{};struct 类名{};struct Hero{};
注意
class方式声明的类型与struct声明的类型仅仅是形式上不同
其唯一的区别在于使用class声明的类型默认成员是私有的(private),
而struct声明的类型默认成员是共有的(public)
3.类的成员变量
//----------------------------.h文件中声明类----------------------------------////.h文件中声明类#ifndef LANDOWNERV2_H#define LANDOWNERV2_H#include <iostream>using namespace std;class LandOwnerv2{public:string name;long score;int cards[20];public:LandOwnerv2(); //构造函数的声明~LandOwnerv2(); //析构函数的声明void Touch_card(int); //声明摸牌方法void show_score(); //声明显示积分方法/*** 在类里定义的方法,如果比较短,就直接在类里实现* 如果方法比较长,则在类里先声明,然后在LandOwner2.cpp文件中定义实现*/};#endif // LANDOWNERV2_H//---------------------/src/xxxxx.cpp文件中实现定义类中方法------------------------------////Sources/src/xxxxx.cpp文件中实现定义类中方法#include "LandOwnerv2.h"LandOwnerv2::LandOwnerv2(){//ctor}//实现摸牌方法//::用来声明Touch_card这个函数是属于LandOwnerv2这个类的//::作用域解析运算符void LandOwnerv2::Touch_card(int cardCount){cout << name << "开始摸"<< cardCount <<"张牌" << endl;}LandOwnerv2::~LandOwnerv2(){//dtor}//----------------------------main.cpp文件中创建类实例-----------------------------------//#include <iostream>#include "LandOwner_v1.h"//如果要使用类,必须包含类的定义文件#include "LandOwnerv2.h"using namespace std;int main(){LandOwner_v1 landOwner1;//定义了一个LandOwner_v1类型变量//调用对象的成员方法//landOwner1.cards[0] = 9;//不能直接使用对象的私有成员landOwner1.TouchCard(100);cout << "Hello world!" << endl;LandOwnerv2 LandOwner2;LandOwner2.name = "栋哥";LandOwner2.Touch_card(20);return 0;}
4.访问修饰符
常见访问修饰符
- public:修饰的成员在任意地方都可以访问
- private:修饰的成员只能够在类中或者友元函数中可以访问
- protected:修饰的成员可以在类中函数,子类函数及友元函数中访问
修饰成员
将修饰关键字放置在类定义的大括号中,添加冒号
```c class 类名(){ 修饰符:
成员列表
};
class LandOwner{ private: string name; public: void PlayCard(); };
-如果不写修饰符则默认为private```cclass LandOwnerv4{private://如果省略了private也代表私有类long score;string name;int cards[20];public:LandOwnerv4();virtual ~LandOwnerv4();long Getscore() { return score; }void Setscore(long val) {if(val < 0){score = 0;//传入的分数有问题}else{score = val;}}//使用工具自动生成Get/Setstring Getname() { return name; }void Setname(string val) { name = val; }//int Getcards[20]() { return cards[20]; }//void Setcards[20](int val) { cards[20] = val; }protected:};#endif // LANDOWNERV4_H
5.构造函数和析构函数
构造函数
- 以类名作为函数名
- 无返回值类型
作用
- 初始化对象的数据成员
- 类对象被创建时,编译器为对象分配内存空间,并自动调用构造函数以完成成员初始化
- 对成员变量进行初始化
构造函数的种类:
- 无参构造
- 一般构造(重载构造)
- 拷贝构造
注意:
- 如果创建的类中没有书写任何构造函数,系统会自动生成默认的无参构造函数(函数为空,什么都不做)
- 如果书写了构造函数,系统就不会再自动生成默认构造,如果希望有一个这样的无参构造函数,需要自己显示地写出来
/**-------------------------LandOwnerv4.cpp--------------------------------定义构造函数,设置对象的初始值*/LandOwnerv4::LandOwnerv4(){//ctor//一般使用构造函数进行成员变量的初始化name = "默认地主";score = 0;//将用户的手牌数组初始化为0memset(cards, 0, sizeof(cards));/**memset()函数,把数组cards的所有元素的值都设置为0,一共要设置cards个元素参数1:要初始化的数组参数2:每个元素要设置的值参数3:数组的大小*/cout << "LandOwern4的无参构造函数(默认构造)被调用"<< endl;cout << "初始化结果如下:" << endl;cout << "名称:" << name << endl;cout << "积分:" << score << endl;cout << "手牌数组:";for(int i = 0; i < sizeof(cards) / sizeof(cards[0]); i++){cout << cards[i] << ".";}cout << endl;}//---------------------------LandOwnerv4.h----------------------------------//#ifndef LANDOWNERV4_H#define LANDOWNERV4_H#include <iostream>#include <memory.h>using namespace std;//使用工具自动生成get/set方法class LandOwnerv4{public:LandOwnerv4();//41行就是构造函数,默认构造写不写都是存在的virtual ~LandOwnerv4();long Getscore() { return score; }void Setscore(long val) {if(val < 0){score = 0;//传入的分数有问题}else{score = val;}}string Getname() { return name; }void Setname(string val) { name = val; }//int Getcards[20]() { return cards[20]; }//void Setcards[20](int val) { cards[20] = val; }protected:private:long score;string name;int cards[20];};#endif // LANDOWNERV4_H//---------------------------main.cpp----------------------------------//int main{LandOwner4 LandOwner01();//默认构造LandOwner4 LandOwner02;//默认构造的省略写法}
5.1 带参构造
//------------------Student.h-----------------------------//#ifndef STUDENT_H#define STUDENT_H#include <iostream>using namespace std;class Student{public://构造函数的重载规则与普通函数相同Student();//这个一个默认构造Student(string name, string dasc);//这是一个带参构造Student(int age);//这是一个只有一个参数的带参构造//如果构造函数中只有一个是唯一的参数,//Student stu4 = 40;可以这样直接定义~Student();void ShowInfo();//这个一个展示任务信息的函数string Getname() { return m_name; }void Setname(string val) { m_name = val; }string Getdesc() { return m_desc; }void Setdesc(string val) { m_desc = val; }int Getage() { return m_age; }void Setage(int val) {if(val < 0){m_age = 18;}elsem_age = val;}protected:private:string m_name;string m_desc;int m_age;};#endif // STUDENT_H//--------------------------Student.cpp---------------------------//#include "Student.h"Student::Student(){//ctorcout << "这是一个默认构造"<<endl;}Student::Student(int age){Setage(age);cout << "调用带参构造:Student(int age)"<<endl;}Student::Student(string name, string desc){m_name = name;//等价写法:SetName(name);m_desc = desc;cout << "调用带参构造:Student(string name, string dasc)"<< endl;}void Student::ShowInfo(){cout << m_desc << m_name << endl;}Student::~Student(){//dtor}//-------------------------main.cpp------------------------//#include <iostream>#include "LandOwner_v1.h"//如果要使用类,必须包含类的定义文件#include "LandOwnerv2.h"#include "LandOwnerv3.h"#include "LandOwnerv4.h"#include "Student.h"using namespace std;int main(){ //LandOwnerv4 andOwnerv4;Student stu1;//在栈内存(速度快)中直接分配空间Student stu2("马化腾","普通家庭");Student stu3(40);cout << "马化腾的年龄是:"<< stu3.Getage() <<endl;stu2.ShowInfo();Student* stu5 = new Student("杰克马","悔创阿里");//在堆内存中分配空间,指针指向堆内存,分配内存的同时,调用构造函数stu5->ShowInfo();//指针访问属性或者方法:->return 0;}
5.2 栈内存和堆内存
栈内的数据可以共享,存取速度非常快,但是栈内存里不能放太多的东西,相当于零售店,看上啥可以直接买走
堆内存相当于大仓库,可以存放更大的数据,速度相对堆内存来说慢一些
5.3 析构函数
析构函数:对象出生的收调用构造函数,结束的时候调用析构函数
- 对象过期时自动调用的特殊成员函数
- 析构函数一般用来完成清理工作
析构函数的名称是在类名前加上==~==
- 析构函数没有参数,只能有一个
注意:
- 析构函数用来释放对象使用的资源,并销毁对象的非static数据成员
- 无论如何时一个对象销毁,都会自动调用其析构函数(隐式析构)
栈空间内的变量自动被释放,堆内存不会被自动释放
- 所以当对象使用完毕时要
delete释放内存
- 所以当对象使用完毕时要
6.this指针
- 每个成员函数(包括构造和析构)都有一个this指针
this指针指向调用的对象,即可以通过this关键字访问当前对象的成员
访问成员变量
- this->变量名
访问成员函数
- this->函数名
