一、C++简介
1.1 C++的产生及其特点
- 从C语言发展演变而来,解决了C语言中存在的一些问题,并增加了对面向对象程序设计方法的支持
- 与其他高级语言相比,C语言可以直接访问物理地址;与汇编相比它具有良好的可读性和可移植性
- C++于1980年由贝尔实验室的Bjarne Stroustrup创建
- 特点:
- 尽量兼容C语言
- 支持面向对象的方法
- 对象是程序的基本单元
- 对象的属性需要用某种类型的数据来表示
- 对象的功能和行为由成员函数来实现,函数的实现归根到底是算法的设计
1.2 Hello World
#include <iostream>
using namespace std;
int main(){
cout << "Hello World !" << endl;
cout << "Welcome to C++ ! " << endl;
return 0;
}
二、C++语法基础
2.1 标识符命名规则
- 以字母或者下划线开始
- 可以由大写字母、小写字母、下划线或数字0~9组成
- 区分大小写
- 不能是C++关键字或操作符
2.2 注释
- 单行注释:
//注释描述
- 多行注释:
/* 注释描述 */
2.3 变量
- 作用:给一段指定的内存空间命名,方便操作这段内存
- 语法:
数据类型 变量名 = 初始值;
#include<iostream>
using namespace std;
int main() {
//变量的定义
//语法:数据类型 变量名 = 初始值
int a = 10;
cout << "a = " << a << endl;
system("pause");
return 0;
}
2.4 常量
- 作用:记录程序中不可更改的数据
- 定义方式:
- #define 宏常量:
#define 常量名 常量值
- const常量:
const 数据类型 常量名 = 常量值
- #define 宏常量:
//1、宏常量
#define day 7
int main() {
cout << "一周里总共有 " << day << " 天" << endl;
//day = 8; //报错,宏常量不可以修改
//2、const修饰变量
const int month = 12;
cout << "一年里总共有 " << month << " 个月份" << endl;
//month = 24; //报错,常量是不可以修改的
system("pause");
return 0;
}
2.5 数据类型
- 作用:
- 存储所需要的尺寸
- 取值空间
- 对齐信息
- 可执行的操作
2.5.1 整型 int
- sizeof关键字
- 作用:统计数据类型所占内存大小
- 语法:
sizeof( 数据类型 / 变量)
- 例如:
sizeof(float)
- 整型内存大小的一个结论:short < int <= long <= long long
2.5.2 浮点型(实型)
-
作用:表示小数
-
分成两种:float、double
-
默认情况下,输出一个小数,会显示出6位有效数字
2.5.3 字符型 char
-
作用:用于显示单个字符
-
语法:
char ch = 'a';
-
注意:
- 用单引号将字符括起
- 单引号内只能有一个字符
-
C/C++中字符型变量只占用一个字节
-
不是把字符本身放到内存中存储,而是将对应的ASCII编码放入存储单元
int main() { char ch = 'a'; cout << ch << endl; cout << sizeof(char) << endl; //ch = "abcde"; //错误,不可以用双引号 //ch = 'abcde'; //错误,单引号内只能引用一个字符 cout << (int)ch << endl; //查看字符a对应的ASCII码 ch = 97; //可以直接用ASCII给字符型变量赋值 cout << ch << endl; system("pause"); return 0; }
-
转义字符:用于表示一些不能显示出来的ASCII字符
-
字符串型
-
C风格:
char 变量名[]="字符串值"
- 要加中括号
int main() { char str1[] = "hello world"; cout << str1 << endl; system("pause"); return 0; }
-
C++风格:
string 变量名="字符串值"
- 要包含头文件:
#include <string>
int main() { string str = "hello world"; cout << str << endl; system("pause"); return 0; }
- 要包含头文件:
-
2.5.4 布尔型 bool
- 只有两个值
-
true:本质是1
-
false:本质是0
- 只占1个字节
int main() {
bool flag = true;
cout << flag << endl; // 1
flag = false;
cout << flag << endl; // 0
cout << "size of bool = " << sizeof(bool) << endl; //1
system("pause");
return 0;
}
2.6 标准库类型string
标准库 string 表示可变长的字符序列
使用string类型时必须要首先包含string头文件
#include <string>
-
定义和初始化
string s1; // 默认初始化,s1是一个空字符串 string s2(s1); // 直接初始化 string s2 = s1; // 拷贝初始化
- 如果使用等号=初始化一个变量,实际上就是执行的拷贝初始化
- 不使用等号=,则执行的直接初始化
string s3(n, 'c'); // 把s3初始化为由连续n个字符c组成的串 // 例如: string s3(5, 'c'); // s3的初始化内容是5个c组成的串:ccccc
-
string对象的操作
- 执行读取操作时,string对象会自动忽略开头的空白,从第一个真正的字符开始读起
- 输入:“ HelloWorld ”,输出:“HelloWorld”
#include <string> int main() { string s; std::cin >> s; std::cout << s << std::endl; return 0; }
- 多个输入或多个输出可以连写在一起
string s1, s2; cin >> s1 >> s2; // 第一个输入读到s1中, 第二个输入读到s2中 cout << s1 << s2 << endl;
-
getline
- 读取一整行
- 可保留字符串中的空白符
- getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,将读入内存的存放到string对象中
- getline遇到换行符就结束读取操作并返回结果
int main() { string line; // 每次循环都是读入一整行, while (getline(cin, line)) { cout << line << endl; } return 0; }
-
empty
- string的成员函数
- 根据string对象是否为空返回一个对应的布尔值
while ( getline(cin, line) ) { if ( !line.empty() ) { count << line << endl; } }
-
size
- string的成员函数
- 返回string对象的长度,即string对象中字符的个数
string line; while (getline(cin, line)) { if (line.size() > 80) cout << line << endl; }
- size函数返回的是一个string::size_type类型的值
2.7 标准库类型vector
vector表示对象的集合,其中所有对象的类型都相同,集合中的每个对象都有一个与之对应的索引,该索引可用于访问对象
vector容纳着其他对象,因此也常称为容器(container)
- 使用vector必须包含vector头文件
#include <vector>
using std::vector;
- vector是一个类模板
- 模板本身不是类或函数,可将其看作编译器生成类或函数编写的一份说明
- 当使用模板时,需要指出编译器应该把类或函数实例化成何种类型
- 实例化:编译器根据模板创建类或函数的过程
-
定义和初始化
vector<T> v1; // T是对象类型,v1是一个空的vector对象,执行默认初始化 vector<T> v2(v1); // v2中包含v1所有元素的副本 vector<T> v2 = v1; // 同上 vector<T> v3(n, val); // v3包含n个重复的元素, 每个元素值都是val // 值初始化 vector<T> v4(n) // 只提供容纳的元素数量,初值由元素类型决定,如int型则初值为0 // 列表初始化 vector<T> v4{a, b, c, ...}; // v4包含了初始值个数的元素,每个元素被赋予相应的初始值 vector<T> v5 = {a, b, c, ...}; // 同上
-
push_back
- vector的成员函数,用于添加元素
- 将一个值作为vector对象的尾元素push到vector对象的尾端
// 将0 ~ 99的整数存储到v2中 vector<int> v2; for (int i = 0; i != 100; ++i) { v2.push_back(i); }
// 从标准输入中读取单词,将其作为vector对象的元素存储 string word; vector<string> text; while ( cin >> word ) { text.push_back(word); }
- 如果循环体内包含有向vector对象添加元素的语句,则不能使用范围for循环
- 范围for循环语句体内不应改变其所遍历序列的大小
注意:
-
不能用下标形式给vector对象添加元素
-
可用于访问已存在的元素,下标从0开始
2.6 数据输入
- 从键盘获取输入数据
int main(){
//整型输入
int a = 0;
cout << "请输入整型变量:" << endl;
cin >> a;
cout << a << endl;
//浮点型输入
double d = 0;
cout << "请输入浮点型变量:" << endl;
cin >> d;
cout << d << endl;
//字符型输入
char ch = 0;
cout << "请输入字符型变量:" << endl;
cin >> ch;
cout << ch << endl;
//字符串型输入
string str;
cout << "请输入字符串型变量:" << endl;
cin >> str;
cout << str << endl;
//布尔类型输入
bool flag = true;
cout << "请输入布尔型变量:" << endl;
cin >> flag;
cout << flag << endl;
system("pause");
return EXIT_SUCCESS;
}
2.7 类型别名
两种方式:
-
typedef
typedef double d; // d是类型double的同义词
-
别名声明:using
using d = double; // 把等号左侧的名字规定成右侧的类型的别名
2.7.1 auto
- 让编译器通过初始值来推算变量的类型
- auto定义的变量必须要有初始值
auto a = val1 + val2;
// a初始化为val1和val2相加的结果
- auto可以在一条语句中声明多个变量,但必须注意一条声明语句只能有一个基本数据类型
auto i = 1, *p = &i; // i是整型变量,p是整型指针
auto a = 0, b = 3.14; // 错误!!!
2.7.2 decltype
- 选择并返回操作数的数据类型
decltype( fcn() ) sum = x; // sum的类型是函数fcn返回类型
- 编译器会分析表达式并得到它的类型,不会实际计算表达式的值
三、运算符
- 用于执行代码运算
3.1 算术运算符
- 处理四则运算
- 加减乘除
int main() {
int a1 = 10;
int b1 = 3;
cout << a1 + b1 << endl;
cout << a1 - b1 << endl;
cout << a1 * b1 << endl;
cout << a1 / b1 << endl; //两个整数相除结果依然是整数
int a2 = 10;
int b2 = 20;
cout << a2 / b2 << endl;
int a3 = 10;
int b3 = 0;
//cout << a3 / b3 << endl; //报错,除数不可以为0
//两个小数可以相除
double d1 = 0.5;
double d2 = 0.25;
cout << d1 / d2 << endl;
return 0;
}
- 取模
int main() {
int a1 = 10;
int b1 = 3;
cout << 10 % 3 << endl;
int a2 = 10;
int b2 = 20;
cout << a2 % b2 << endl;
int a3 = 10;
int b3 = 0;
//cout << a3 % b3 << endl; //取模运算时,除数也不能为0
//两个小数不可以取模
double d1 = 3.14;
double d2 = 1.1;
//cout << d1 % d2 << endl;
return 0;
}
- 递增递减
int main() {
//后置递增
int a = 10;
a++; //等价于a = a + 1
cout << a << endl; // 11
//前置递增
int b = 10;
++b;
cout << b << endl; // 11
//区别
//前置递增先对变量进行++,再计算表达式
int a2 = 10;
int b2 = ++a2 * 10;
cout << b2 << endl;
//后置递增先计算表达式,后对变量进行++
int a3 = 10;
int b3 = a3++ * 10;
cout << b3 << endl;
return 0;
}
3.2 赋值运算符
3.3 比较运算符
- 用于表达式的比较,并返回一个真值或假值
3.4 逻辑运算符
- 用于根据表达式的值返回真值或假值
四、基本控制结构
C/C++支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构
- 顺序结构:程序按顺序执行,不发生跳转
- 选择结构:依据条件是否满足,有选择的执行相应功能
- 循环结构:依据条件是否满足,循环多次执行某段代码
4.1 选择结构
4.1.1 if语句
- 单行格式 if 语句
int main() {
//选择结构-单行if语句
//输入一个分数,如果分数大于600分,视为考上一本大学,并在屏幕上打印
int score = 0;
cout << "请输入一个分数:" << endl;
cin >> score;
cout << "您输入的分数为: " << score << endl;
//if语句
//注意事项,在if判断语句后面,不要加分号
if (score > 600) {
cout << "我考上了一本大学!!!" << endl;
}
return 0;
}
- 多行格式 if 语句
int main() {
int score = 0;
cout << "请输入考试分数:" << endl;
cin >> score;
if (score > 600) {
cout << "我考上了一本大学" << endl;
}
else {
cout << "我未考上一本大学" << endl;
}
return 0;
}
- 多条件的 if 语句
int main() {
int score = 0;
cout << "请输入考试分数:" << endl;
cin >> score;
if (score > 600) {
cout << "我考上了一本大学" << endl;
}
else if (score > 500) {
cout << "我考上了二本大学" << endl;
}
else if (score > 400) {
cout << "我考上了三本大学" << endl;
}
else {
cout << "我未考上本科" << endl;
}
return 0;
}
- 嵌套if语句
// 案例需求:
// 提示用户输入一个高考考试分数,根据分数做如下判断
// 分数如果大于600分视为考上一本,大于500分考上二本,大于400考上三本,其余视为未考上本科
// 在一本分数中,如果大于700分,考入北大,大于650分,考入清华,大于600考入人大
int main() {
int score = 0;
cout << "请输入考试分数:" << endl;
cin >> score;
if (score > 600) {
cout << "我考上了一本大学" << endl;
if (score > 700) {
cout << "我考上了北大" << endl;
}
else if (score > 650) {
cout << "我考上了清华" << endl;
}
else {
cout << "我考上了人大" << endl;
}
}
else if (score > 500) {
cout << "我考上了二本大学" << endl;
}
else if (score > 400) {
cout << "我考上了三本大学" << endl;
}
else {
cout << "我未考上本科" << endl;
}
return 0;
}
4.1.2 三目运算符
- 语法:
表达式1 ? 表达式2 :表达式3
- 如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
- 如果表达式1的值为假,执行表达式3,并返回表达式3的结果。
int main() {
int a = 10, b = 20, 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;
cout << "c = " << c << endl;
return 0;
}
4.1.3 switch语句
- 执行多条件分支语句
- switch语句中表达式类型只能是整型或者字符型
- case里如果没有break,那么程序会一直向下执行
switch(表达式) {
case 结果1:执行语句;break;
case 结果2:执行语句;break;
...
default:执行语句;break;
}
//请给电影评分
//10 ~ 9 经典
// 8 ~ 7 非常好
// 6 ~ 5 一般
// 5分以下 烂片
int main() {
int score = 0;
cout << "请给电影打分" << endl;
cin >> score;
switch (score) {
case 10:
case 9:
cout << "经典" << endl;
break;
case 8:
cout << "非常好" << endl;
break;
case 7:
case 6:
cout << "一般" << endl;
break;
default:
cout << "烂片" << endl;
break;
}
return 0;
}
4.2 循环语句
4.2.1 while循环
- 满足循环条件,执行循环语句
int main() {
int num = 0;
while (num < 10) {
cout << "num = " << num << endl;
num++;
}
return 0;
}
4.2.2 do…while循环
- 与while的区别在于do…while会先执行一次循环语句,再判断循环条件
int main() {
int num = 0;
do {
cout << num << endl;
num++;
} while (num < 10);
return 0;
}
4.2.3 for循环
- 语法:
for(起始表达式; 条件表达式; 末尾循环体) { 循环语句; }
int main() {
for (int i = 0; i < 10; i++) {
cout << i << endl;
}
return 0;
}
4.3 break语句
-
用于跳出选择结构或者循环结构
-
break使用的时机:
- 出现在switch条件语句中,作用是终止case并跳出switch
- 出现在循环语句中,作用是跳出当前的循环语句
- 出现在嵌套循环中,跳出最近的内层循环语句
int main() {
//2、在循环语句中用break
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; //跳出循环语句
}
cout << i << endl;
}
return 0;
}
4.4 continue语句
- 在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环
int main() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
continue;
}
cout << i << endl;
}
return 0;
}
五、迭代器
string对象的字符或者vector对象的元素都可以使用下标进行访问,但在C++标准库容器中只有少数几种支持下标运算符
迭代器(iterator)是一种更加通用的元素访问方式,所有的标准库容器均可使用
- 类似指针,迭代器提供对对象的间接访问
- 与指针不同的是迭代器不使用取址符号,有迭代器类型,同时有返回迭代器的成员
5.1、迭代器使用
-
begin成员用于返回指向第一个元素(或第一个字符)的迭代器
-
end成员返回容器(或string对象)末尾元素的下一个位置的迭代器
- end返回的迭代器是容器中不存在的一个尾后元素,没有实际含义,仅仅是一个标记
- 常被称为尾后迭代器或简称尾迭代器
-
如果容器为空,那么begin和end返回的是同一个迭代器
5.1.1 引用和解引用
*iter // 返回迭代器iter所指元素的引用
iter->mem // 解引用iter,并获取该元素的名为mem的成员,等价于:(*iter).mem
- 可通过解引用迭代器来获取它所指的元素,执行解引用的迭代器必须合法并确实指示着某个值
string s("some string");
if (s.begin() != s.end()) { // 确保s非空
auto it = s.begin(); // 声明一个迭代器变量it并将begin返回结果赋给他, 即得到指示s中首字符的迭代器
*it = toupper(*it); // 通过解引用运算符*获取该迭代器指向的元素
}
- 解引用可以获得迭代器所指对象,如果该对象类型是类,那么就可以进一步访问其成员
(*it).empty()
it是一个字符串组成的vector对象,则可以用empty成员函数检查其是否为空
-
解引用运算符(*)的运算优先级低于点运算符(.),因此迭代器解引用必须加上圆括号
-
箭头运算符( -> ):将解引用和成员访问两个操作结合在一起
it->mem
// 等价于 (*it).mem
5.1.2 将迭代器从一个元素移动到另外一个元素
++iter // 令iter指示容器中的下一个元素
--iter // 令iter指示容器中的上一个元素
例子:使用迭代器遍历string对象
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) {
*it = toupper(*it); // 解引用it, 将结果传入toupper函数得到对应大写形式
}
// 首先用s.begin的返回值初始化it, it指示的是s中的第一个字符
// 条件部分检查是否到达s的尾部( it != s.end() ), 如果没有则将it解引用结果传入isspace函数检查是否遇到空白
// 每次迭代的最后,执行++it令迭代器前移一个位置以访问下一个元素
- end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用操作
5.2、迭代器运算
iter + n // 迭代器加上一个整数值仍得一个迭代器
iter - n // 迭代器减去一个整数值仍得一个迭代器
iter += n // iter = iter + n
iter -= n // iter = iter - n
iter1 - iter2 // 两个迭代器之间的距离
> 、>= 、< 、 <= // 两个迭代器的关系运算符,一个迭代器iter1的位置在另一个迭代器iter2位置之前, 则iter1 < iter2
例子:二分搜索,寻找sought
// text必须是有序的
// beg和end表示我们搜索的范围
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/2; // 初始状态下的中间点
// 当还有元素没有检查且还没找到sought时执行循环
while (mid != end && *mid != sought) {
if (sought < *mid) { // 判断是否在前半部分
end = mid; // 如果是,则忽略掉后半部分
} else { // 不在前半部分那就在后半部分
beg = mid + 1; // 调整搜索范围,在mid之后开始找
}
mid = beg + (end - beg)/2; // 计算新的中间点
}
六、数组
数组是一种类似vector的数据结构,也是存放类型相同的对象的容器,这些对象没有名字,需要通过其位置访问
与vector的不同点:
- 数组的大小确定不变,不能随意向数组中增加元素
6.1、定义和初始化内置数组
- 声明语法:
a[d]
- a是数组名称
- d是数组维数,说明了数组中元素的个数(大于0),其必须为常量表达式
- 定义数组时必须指定数组类型,不允许用auto关键字
- 数组的元素是对象,因此不存在引用的数组
- 不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值
int arr1[10];
int arr2[] = {0, 1, 2, 3};
string arr3[4] = {"hi", "bye"};
int (*Parray)[10] = &arr; // Parray指向一个含有十个整数的数组
int (&arrRef)[10] = arr; // arrRef引用一个含有十个整数的数组
- 理解数组声明的含义,最好的办法是从数组名字开始按照由内向外的顺序阅读
6.2、访问数组元素
- 数组索引从0开始
- 遍历数组所有元素时,最好的办法是使用范围for循环
for (auto i : score) {
cout << i << " ";
}
cout << endl;
6.3、多维数组
- 当数组的元素仍为数组时,相当于数组的数组,通常使用两个维度来定义
a[m][n]
- a是数组名
- m是数组本身的维数
- n是数组元素的维数
- 由内向外阅读
int a[3][2] = {
{0, 1},
{2, 3},
{4, 5}
};
int a[3][2] = {0, 1, 2, 3, 4, 5};
- 使用下标运算符访问元素
- 数组的每个维度对应一个下标运算符
// 两层嵌套的for循环遍历多维数组
constexpr size_t rosCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt]; // 3*4的多维数组,外层数组3个元素,内层数组4个元素
for (size_t i = 0; i != rowCnt; ++i) { // 外层for循环遍历ia所有元素
for (size_t j = 0; j != colCnt; ++j) { // 内层for循环遍历数组元素中的整数元素
ia[i][j] = i * colCnt + j;
}
}
6.3.1 使用范围for循环遍历多维数组
for (declaration : expression) {
// 循环体
}
// declaration: 表示此处要定义一个变量, 该变量的类型为要遍历序列中存储元素的类型
// C++11中, 可以用auto自动获取类型
// expression: 表示要遍历的序列
// 常见的可为已定义的普通数组或者容器
// 还可以是用 { } 初始化的序列, 例如:{1, 2, 3, 4}
- 范围for循环遍历序列时,定义的变量declaration表示的是当前序列中的各个元素
- 不是以迭代器的形式在遍历
例子:
std::vector<int> arr = {1, 2, 3, 4, 5};
for (auto val : arr) {
std::cout << val << std::endl;
arr.push_back(10);
}
constexpr size_t rosCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt]; // 3*4的多维数组,外层数组3个元素,内层数组4个元素
// 使用范围for循环遍历就不需要去管理下标索引,而是将索引管理交给系统来完成
size_t cnt = 0;
for (auto &row : ia)
for (auto &col : row) {
col = cnt;
++cnt;
}
七、函数
7.1、函数的定义
函数的组成:
- 返回类型
- 函数名字
- 0个或多个形参组成的参数列表
- 函数体
7.2、函数的声明
- 与定义的唯一区别:函数声明无须函数体
- 函数声明也称为函数原型
- 函数的三要素描述了函数的接口
- 返回类型
- 函数名
- 形参类型
void print (vector<int>::const_iterator beg,
vector<int>::const_iterator end);
- 一般在头文件中进行声明,在源文件中定义
- 含有函数声明的头文件应该被包含到定义函数的源文件中
7.3、参数传递
每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化
- 引用传递:形参是引用类型,那么它将直接绑定到对应的实参上
- 实参被引用传递
- 函数被传引用调用
- 值传递:实参的值被拷贝给形参,形参和实参是两个相互独立的对象
- 实参被值传递
- 函数被传值调用
- 函数对形参做的所有操作都不会影响实参
7.3.1 传引用参数
- 对于引用的操作实际上是作用在引用所引的对象上
- 通过使用引用形参,允许函数改变实参的值
void reset(int &val) {
val = 0;
}
// 调用reset函数, 将i置零
int i = 10;
reset(i);
cout << "i = " << i << endl; // 输出: i = 0
- 如果函数无须改变引用形参的值,最好将其声明为常量引用
// 比较两个string对象的长度, 不需要改变string对象的内容, 因此定义成常量引用
bool isShorter(const string &s1, const string &s2) {
return s1.size() < s2.size()
}
7.3.2 数组形参
在定义和使用作用在数组上的函数时要注意两点:
- 不允许拷贝数组
- 使用数组时通常会将其转换成指针
由于上述这两点性质,无法通过值传递的方式使用数组参数
// 形式不同, 但这三个print函数等价
// 每个函数都有一个const int*类型的形参
void print(const int*);
void print(const int[]);
void print(const int[10]); // 维度表示期望数组含有多少个元素, 实际上不一定
// 编译器只检查传入的参数是否是const int*类型
- 如果传给print函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组大小对函数的调用无影响
7.4、返回类型和return语句
return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方
// 两种语句形式:
return;
return expression;
- 无返回值函数
- 没有返回值的return语句只能用在返回类型是void的函数中
- 返回void的函数不要求非得有return语句,在该类函数的最后一句会隐式的执行return
- void函数想在中间位置提前退出,那么可以使用return语句
- 没有返回值的return语句只能用在返回类型是void的函数中
- 有返回值函数
- 只要函数的返回类型不是void,则该函数内的每条return语句必须返回一个值
- return语句返回值的类型必须与函数的返回类型相同
7.5、函数的分文件编写
作用:让代码结构更加清晰
步骤:
- 创建后缀名为 .h 的头文件
- 在头文件中写函数的声明
- 创建后缀名为 .cpp 的源文件
- 在源文件中写函数的定义
- 引用上一步的.h头文件
- 在主函数中调用函数,引用该函数的头文件
示例:
swap.h头文件
# include <iostream>
//实现两个数字交换的函数声明
void swap(int a, int b);
swap.cpp源文件
// 双引号""代表自定义的头文件
# include "swap.h"
void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}
main.c主程序文件
# include <iostream>
# include "swap.h"
int main(){
int a = 10;
int b = 20;
swap(a, b);
return 0;
}
八、指针
8.1、指针的基本概念
- 作用:间接访问内存
- 内存编号是从0开始记录的,一般用十六进制数字表示
- 可利用指针变量保存地址
- 所有指针类型在32位操作系统下是4个字节
- 指针变量和普通变量的区别
- 普通变量存放的是数据,指针变量存放的是地址
- 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
- 语法:
数据类型* 变量名;
int main() {
//1、指针的定义
int a = 10; //定义整型变量a
//指针定义语法: 数据类型 * 变量名 ;
int * p;
//指针变量赋值
p = &a; //指针指向变量a的地址
cout << &a << endl; //打印数据a的地址
cout << p << endl; //打印指针变量p
//2、指针的使用
//通过*操作指针变量指向的内存
cout << "*p = " << *p << endl;
return 0;
}
8.2、const指针
- 看const右侧紧跟着的是指针还是常量,是指针就是常量指针,是常量就是指针常量
- const修饰指针 —— 常量指针
const int* p = &a;
指针的指向可以修改,但是指针指向的值不可以修改
理解:const修饰的是(*p),这是指针p指向的值
- const修饰常量 —— 指针常量
int *const p = &a;
指针的指向不可以改,指针指向的值可以改
理解:const修饰的是p,p是一个整型指针,即指针指向的值不可以改
-
const既修饰指针,又修饰常量
const int *const p = &a;
指针的指向和指针指向的值都不可以改
8.3、指针和数组
-
利用指针访问数组中元素
-
数组名表示首元素的地址
string nums[] = {"one", "two", "three"}; string *p = nums[0]; // p指向nums的第一个元素“one” string *p1 = nums; // 等价上述语句
-
指针也是迭代器
-
运行使用递增运算符将指向数组元素的指针向前移动到下一个位置上
int arr[] = {0, 1, 2, 3, 4, 5, 6}; int *p = arr; // p指向arr的第一个元素 ++p; // p指向arr[1]
-
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++;
}
return 0;
}
-
begin和end
-
数组不是类类型,因此begin和end不是成员函数
-
将数组作为begin和end函数的参数
#include <iterator> // begin和end两个函数定义在iterator头文件中 int main() { int a[] = {0, 1, 2, 3, 4, 5, 6}; int *beg = begin(a); // 指向a首元素的指针 int *last = end(a); // 指向a尾元素下一个位置的指针 }
-
-
指针和多维数组
- 使用多维数组名称时,会自动将其转换成指向数组首元素的指针
- 多维数组名转换得来的指针是指向第一个内层数组的指针
int ia[3][4]; for (auto p = ia; p != ia + 3; ++p) { for ( auto q = *p; q != *p + 4; ++q) { cout << *q << ' '; } cout << end; }
8.4、指针和函数
- 利用指针作函数参数,可以修改实参的值
- 如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
//值传递
void swap1(int a ,int b) {
int temp = a;
a = b;
b = temp;
}
//地址传递
void swap2(int * p1, int *p2) {
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main() {
int a = 10;
int b = 20;
swap1(a, b); // 值传递不会改变实参
swap2(&a, &b); //地址传递会改变实参
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
8.5、冒泡排序
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个;
- 对每一对相邻元素做同样的工作,执行完毕后,找到第一个最大值;
- 重复以上步骤,每次比较次数-1,直到不需要比较。
- 排序总轮数 = 元素个数 - 1
- 每轮对比次数 = 元素个数 - 排序轮数 - 1
// 利用冒泡排序实现升序序列
#include <iostream>
// 冒泡排序函数 参数1 数组的首地址 参数2 数组长度
void bubbleSort(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 printArray(int* arr, int len) {
for (int i = 0; i < len; i++) {
std::cout << arr[i] << std::endl;
}
}
int main() {
// 1、创建数组
int arr[10] = { 4, 3, 6, 9, 1, 2, 10, 8, 7, 5 };
int len = sizeof(arr) / sizeof(arr[0]);
// 2、创建函数,实现冒泡排序
bubbleSort(arr, len);
// 3、打印排序后的数组
printArray(arr, len);
system("pause");
return 0;
}
九、结构体
9.1、定义和使用
- 用户自定义的数据类型,允许用户存储不同的数据类型
- 语法:
struct 结构体名 { 结构体成员列表 };
- 结构体变量创建方式:
- struct 结构体名 变量名
- struct 结构体名 变量名 = { 成员1值 , 成员2值…}
- 定义结构体时顺便创建变量
- 结构体变量利用操作符 “ . ” 访问成员
//结构体定义
struct student {
//成员列表
string name; //姓名
int age; //年龄
int score; //分数
} stu3; //结构体变量创建方式3
int main() {
//结构体变量创建方式1
struct student stu1; //struct 关键字可以省略
stu1.name = "张三";
stu1.age = 18;
stu1.score = 100;
cout << "姓名:" << stu1.name << " 年龄:" << stu1.age << " 分数:" << stu1.score << endl;
//结构体变量创建方式2
struct student stu2 = { "李四", 19, 60 };
cout << "姓名:" << stu2.name << " 年龄:" << stu2.age << " 分数:" << stu2.score << endl;
stu3.name = "王五";
stu3.age = 18;
stu3.score = 80;
cout << "姓名:" << stu3.name << " 年龄:" << stu3.age << " 分数:" << stu3.score << endl;
return 0;
}
9.2、结构体数组
- 将自定义的结构体放入到数组中方便维护
- 语法:
struct 结构体名 数组名[元素个数] = { {} , {} , ... {} }
//结构体定义
struct student {
string name; //姓名
int age; //年龄
int score; //分数
}
int main() {
//结构体数组
struct student arr[3] = {
{"张三", 18, 80 },
{"李四", 19, 60 },
{"王五", 20, 70 }
};
for (int i = 0; i < 3; i++) {
cout << "姓名:" << arr[i].name << " 年龄:" << arr[i].age << " 分数:" << arr[i].score << endl;
}
return 0;
}
9.3、结构体指针
- 通过指针访问结构体中的成员
- 利用操作符
->
可以通过结构体指针访问结构体属性
struct student {
string name; //姓名
int age; //年龄
int score; //分数
};
int main() {
struct student stu = { "张三", 18, 100 };
struct student * p = &stu;
p->score = 80; //指针通过 -> 操作符可以访问成员
cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;
return 0;
}
9.4、结构体嵌套
//学生结构体定义
struct student {
string name; //姓名
int age; //年龄
int score; //分数
};
//教师结构体定义
struct teacher {
int id; //职工编号
string name; //教师姓名
int age; //教师年龄
struct student stu; //子结构体 学生
};
int main() {
struct teacher t1;
t1.id = 10000;
t1.name = "老王";
t1.age = 40;
t1.stu.name = "张三";
t1.stu.age = 18;
t1.stu.score = 100;
cout << "教师 职工编号: " << t1.id << " 姓名: " << t1.name << " 年龄: " << t1.age << endl;
cout << "辅导学员 姓名: " << t1.stu.name << " 年龄:" << t1.stu.age << " 考试分数: " << t1.stu.score << endl;
return 0;
}
9.5、结构体做函数参数
- 将结构体作为参数向函数中传递
- 值传递和地址传递两种方式
//学生结构体定义
struct student {
string name; //姓名
int age; //年龄
int score; //分数
};
//值传递
void printStudent(student stu ) {
stu.age = 28;
cout << "子函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
}
//地址传递
void printStudent2(student *stu) {
stu->age = 28;
cout << "子函数中 姓名:" << stu->name << " 年龄: " << stu->age << " 分数:" << stu->score << endl;
}
int main() {
student stu = { "张三",18,100};
//值传递
printStudent(stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
//地址传递
printStudent2(&stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
return 0;
}
9.6、const
- 用const来防止误操作
struct student {
string name; //姓名
int age; //年龄
int score; //分数
};
//const使用场景
//加const防止函数体中的误操作
void printStudent(const student *stu) {
//stu->age = 100; //操作失败,因为加了const修饰
cout << "姓名:" << stu->name << " 年龄:" << stu->age << " 分数:" << stu->score << endl;
}
int main() {
student stu = { "张三",18,100 };
printStudent(&stu);
return 0;
}