C++ 基础

从 hello world 到通讯录管理系统:变量、数据类型、运算符、流程结构、数组、函数、指针、结构,一路把 C 语言底子搬到 C++。

hello C++

#include <iostream>
using namespace std;

int main()
{
    cout << "hello C++" << endl;
    system("pause");
    return 0;
}

C++ 中以流的形式进行输入输出,而不再是 printf 和 scanf;头文件也要修改成 iostream,不需要 .h。

using namespace std; 是什么含义???

endl 可以省略,作用为换行。

变量 常量 命名

变量

作用:给一段指定的内存空间起名,方便操作这段内存。

语法:数据类型 变量名 = 初始值;

#include <iostream>
using namespace std;

int main() {
    // 变量的定义
    // 语法:数据类型 变量名 = 初始值
    int a = 10;
    cout << "a = " << a << endl;
    system("pause");
    return 0;
}

注意:C++ 在创建变量时,必须给变量一个初始值,否则会报错。

常量

作用:用于记录程序中不可更改的数据。

两种方式:

#define 宏常量
#define 常量名 常量值

通常在文件上方定义,表示一个常量。

const 修饰的变量

语法:const 数据类型 常量名 = 常量值

通常在变量定义前加关键字 const,修饰该变量为常量,不可修改。

#include <iostream>
using namespace std;

// 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;
}

关键字

关键字是 C++ 中预先保留的单词(标识符)。

cpp-image1

标识符命名规则

作用:C++ 规定给标识符(变量、常量)命名时,有一套自己的规则。

(1)标识符不能是关键字

(2)标识符只能由字母、数字、下划线组成

(3)第一个字符必须为字母或下划线

(4)标识符中字母区分大小写

给标识符命名时,争取做到见名知意的效果,方便自己和他人的阅读。

数据类型

C++ 规定在创建一个变量或者常量时,必须要指定出相应的数据类型,否则无法给变量分配内存。

整型

作用:整型变量表示的是整数类型的数据。

C++ 中能够表示整型的类型有以下几种方式,区别在于所占内存空间不同:

cpp-image2

实型(浮点型)

作用:用于表示小数。

浮点型变量分为两种:单精度 float、双精度 double。两者的区别在于表示的有效数字范围不同。

cpp-image3

使用 float a = 3.14; 会进行转换,因为计算机看到 3.14 就认为是 double——实际上这条语句还做了一次把 double 3.14 转换成 float 3.14 的操作。所以在使用 float 的时候可以采用 float a = 3.14f;,告诉计算机这是单精度。

cpp-image4 cpp-image5

使用 cout 输出时默认只会保留六位有效数字。

cpp-image6

字符型

作用:字符型变量用于显示单个字符。

语法:char ch = 'a';

注意 1:在显示字符型变量时,用单引号将字符括起来,不要用双引号。

注意 2:单引号内只能有一个字符,不可以是字符串。

C 和 C++ 中字符型变量只占用 1 个字节。字符型变量并不是把字符本身放到内存中存储,而是将对应的 ASCII 编码放入到存储单元。

ASCII 码大致由以下两部分组成:

  • ASCII 非打印控制字符:ASCII 表上的数字 0-31 分配给了控制字符,用于控制像打印机等一些外围设备。
  • ASCII 打印字符:数字 32-126 分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。

tips:按住 alt 再按一个数字,就可以打出它的 ASCII 码对应的字符。

对于一些不能显示出来的 ASCII 字符:

cpp-image7

要用到转义字符。

sizeof 关键字

作用:利用 sizeof 关键字可以统计数据类型所占内存大小。

语法:sizeof(数据类型 / 变量)

int a = 3;
sizeof(int);
sizeof(a);

字符串

作用:用于表示一串字符。

两种风格:

C 风格字符串char 变量名[] = "字符串值"

char str1[] = "hello world";
cout << str1 << endl;

C++ 风格字符串:需要加入头文件 #include <string>,语法:string 变量名 = "字符串值"

string str = "hello world";
cout << str << endl;

在 "" 里的就是字符串,直接出现在 cout 中也是一样。字符串存放在一个比较高的地址。所谓字符串类型也不过就是把这串字母做一个打包、给它一个名字,以实现再次使用时无需重新打出一整串,而只要用一个 str1 或者 str 就行。

bool 类型

作用:布尔数据类型代表真或假的值。

bool 类型只有两个值:

  • true --- 真(本质是 1)
  • false --- 假(本质是 0)

bool 类型占1 个字节大小。

输入输出

作用:用于从键盘获取数据。

关键字:cin。语法:cin >> 变量

cpp-image8
// 整型输入
int a;
cout << "请输入整型变量:";
cin >> a;
cout << a << endl;

// 浮点型输入
double d;
cout << "请输入浮点型变量:";
cin >> d;
cout << d << endl;

// 字符型输入
char ch;
cout << "请输入字符型变量:";
cin >> ch;
cout << ch << endl;

// 字符串型输入
string str;
cout << "请输入字符串型变量:";
cin >> str;
cout << str << endl;

// 布尔类型输入
bool flag;
cout << "请输入布尔型变量:";
cin >> flag;
cout << flag << endl;

运算符

作用:用于执行代码的运算。

运算符类型 作用
算术运算符 用于处理四则运算
赋值运算符 用于将表达式的值赋给变量
比较运算符 用于表达式的比较,并返回一个真值或假值
逻辑运算符 用于根据表达式的值返回真值或假值

算术运算符

作用:用于处理四则运算。

算术运算符包括以下符号:

运算符 术语 示例 结果
+ 正号 +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;

注意:

  • 在除法运算中,除数不能为 0
  • 只有整型变量可以进行取模运算
  • 前置递增先对变量进行 ++,再计算表达式,后置递增相反

赋值运算符

作用:用于将表达式的值赋给变量。

赋值运算符包括以下几个符号:

运算符 术语 示例 结果
= 赋值 a=2; b=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;

比较运算符

作用:用于表达式的比较,并返回一个真值或假值。

比较运算符有以下符号:

运算符 术语 示例 结果
相等于 4 3 0
!= 不等于 4 != 3 1
< 小于 4 < 3 0
> 大于 4 > 3 1
<= 小于等于 4 <= 3 0
>= 大于等于 4 >= 1 1

注意:C 和 C++ 语言的比较运算中,"真"用数字 "1" 来表示,"假"用数字 "0" 来表示。

逻辑运算符

作用:用于根据表达式的值返回真值或假值。

逻辑运算符有以下符号:

运算符 术语 示例 结果
! !a 如果 a 为假,则 !a 为真;如果 a 为真,则 !a 为假。
&& a && b 如果 a 和 b 都为真,则结果为真,否则为假。
|| a || b 如果 a 和 b 有一个为真,则结果为真,二者都为假时,结果为假。

非:真变假,假变真。

与:同真为真,其余为假。

或:同假为假,其余为真。

程序流程结构

C/C++ 支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构。

  • 顺序结构:程序按顺序执行,不发生跳转
  • 选择结构:依据条件是否满足,有选择的执行相应功能
  • 循环结构:依据条件是否满足,循环多次执行某段代码

选择结构

if 语句

作用:执行满足条件的语句。

if 语句的三种形式:单行格式 if 语句、多行格式 if 语句、多条件的 if 语句。

cpp-image9

单行格式 if 语句:

if (条件)
{
    条件满足执行的语句;
}

多行格式 if 语句:

cpp-image10
if (条件)
{
    条件满足执行的语句;
}
else
{
    条件不满足执行的语句;
}

多条件的 if 语句:

cpp-image11
if (条件1)
{
    条件1满足执行的语句;
}
else if (条件2)
{
    条件2满足执行的语句;
}
...
else
{
    都不满足执行的语句;
}

嵌套 if 语句:在 if 语句中,可以嵌套使用 if 语句,达到更精确的条件判断。

三目运算符

作用:通过三目运算符实现简单的判断。

语法:表达式1 ? 表达式2 : 表达式3

  • 如果表达式 1 的值为真,执行表达式 2,并返回表达式 2 的结果
  • 如果表达式 1 的值为假,执行表达式 3,并返回表达式 3 的结果

返回的是变量,可以继续当成左值被赋值:

int a = 10;
int b = 20;
(a > b ? a : b) = 100;

相当于给 b 赋值 100。和 if 语句比较,三目运算符优点是短小整洁,缺点是如果用嵌套,结构不清晰。

switch 语句

作用:执行多条件分支语句。

switch(表达式)
{
    case 结果1: 执行语句; break;
    case 结果2: 执行语句; break;
    ...
    default: 执行语句; break;
}

注意:

  • switch 语句中表达式类型只能是整型或者字符型
  • case 里如果没有 break,那么程序会一直向下执行
  • 与 if 语句比,对于多条件判断时,switch 的结构清晰,执行效率高;缺点是 switch 不可以判断区间

循环结构

while

作用:满足循环条件,执行循环语句。

while (循环条件)
{
    循环语句;
}

只要循环条件的结果为真,就执行循环语句。在执行循环语句时候,程序必须提供跳出循环的出口,否则出现死循环。

do...while

作用:满足循环条件,执行循环语句。

do
{
    循环语句;
} while (循环条件);

与 while 的区别在于 do...while 会先执行一次循环语句,再判断循环条件。

for

作用:满足循环条件,执行循环语句。

for (起始表达式; 条件表达式; 末尾循环体)
{
    循环语句;
}

for 循环中的表达式,要用分号进行分隔。三者都是常用的循环语句,for 循环结构比较清晰,用的较多。

跳转语句

break 语句

作用:用于跳出选择结构、循环结构。

break 使用的时机:

  • 出现在 switch 条件语句中,作用是终止 case 并跳出 switch
  • 出现在循环语句中,作用是跳出当前的循环语句
  • 出现在嵌套循环中,跳出最近的内层循环语句
continue 语句

作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。

continue 并没有使整个循环终止,而 break 会跳出循环。找奇数:

for (int i = 0; i < 100; i++)
{
    if (i % 2 == 0)
        continue;
    cout << i << endl;
}
goto 语句

作用:可以无条件跳转语句。

语法:goto 标记;

解释:如果标记的名称存在,执行到 goto 语句时,会跳转到标记的位置。在程序中不建议使用 goto 语句,以免造成程序流程混乱。

练习:C++ 猜数

#include <iostream>
#include <ctime>
using namespace std;

int main()
{
    srand((unsigned int)time(NULL));
    int num = rand() % 100 + 1;
    int val;
    while (1) {
        cin >> val;
        if (val > num) {
            cout << "大了" << endl;
        }
        else if (val < num) {
            cout << "小了" << endl;
        }
        else {
            cout << "猜对了" << endl;
            break;
        }
    }
    system("pause");
    return 0;
}

仍然是用系统时间作为随机数种子,不一样的地方在于头文件变成了 ctime。

敲桌子

输出 1~100 中所有不含 7 的数(7 的倍数或含数字 7 的"敲桌子"):

#include <iostream>
using namespace std;

int main()
{
    for (int i = 1; i <= 100; i++)
    {
        if (i % 7 == 0 || i % 10 == 7 || i / 10 == 7)
            cout << "敲桌子" << endl;
        else
            cout << i << endl;
    }
    system("pause");
    return 0;
}

九九乘法表

#include <iostream>
using namespace std;

int main()
{
    for (int i = 1; i < 10; i++)
    {
        for (int j = 1; j <= i; j++)
            cout << j << "*" << i << "=" << j * i << "\t";
        cout << endl;
    }
    system("pause");
    return 0;
}

cout << endl; 相当于 printf("\n");。cout 采用的就不再是之前填空的方式了,貌似更直接了当:输出什么、再输出什么。

数组

所谓数组,就是一个集合,里面存放了相同类型的数据元素。

  • 特点 1:数组中的每个数据元素都是相同的数据类型
  • 特点 2:数组是由连续的内存位置组成的

一维数组

一维数组定义的三种方式:

数据类型 数组名[数组长度];
数据类型 数组名[数组长度] = { 值1, 值2 ... };
数据类型 数组名[] = { 值1, 值2 ... };

注意:数组名的命名规范与变量名命名规范一致,不要和变量重名;数组中下标是从 0 开始索引。

数组名的用途:

  • 统计整个数组在内存中的长度:
cout << "整个数组所占内存空间为: " << sizeof(arr) << endl;
  • 获取数组在内存中的首地址:
cout << "数组首地址为: " << (int)arr << endl;

在 C++ 中无需使用 %d、%p 等表示输出的格式。此外还有一些小技巧:

cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl;
cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;

通过下标找地址,可以发现每个单元间都隔着一个类型的长度:

cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl; // 与 arr 的地址一样
cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;

总结:

  • 直接打印数组名,可以查看数组所占内存的首地址
  • 对数组名进行 sizeof,可以获取整个数组占内存空间的大小
  • 数组名是常量,不可以作为左值被赋值(const 指针)
示例

找最大数

int arr[] = { 20,40,27,55,36 };
int max = arr[0];

for (int i = 0; i + 1 < sizeof(arr) / sizeof(arr[0]); i++)
{
    int x = arr[i] > arr[i + 1] ? arr[i] : arr[i + 1]; // 相邻两个取大
    max = x > max ? x : max;
}
cout << max << endl;

数组逆序

int arr[] = { 1,2,3,4,5 };
int first = 0;
int last = sizeof(arr) / sizeof(arr[0]) - 1;
int n;

for (; first < last; first++, last--)
{
    n = arr[first];
    arr[first] = arr[last];
    arr[last] = n;
}

for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    cout << arr[i];

冒泡排序

int arr[9] = { 4,2,8,0,5,7,1,3,9 };

for (int i = 0; i < sizeof(arr) / sizeof(arr[0]) - 1; i++)
{
    for (int j = 0; j < sizeof(arr) / sizeof(arr[0]) - 1 - i; j++)
    {
        if (arr[j] > arr[j + 1])
        {
            int temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
        }
    }
}

for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    cout << arr[i] << endl;

单步运行观察 temp 的交换过程:

cpp-image12

二维数组

二维数组就是在一维数组上,多加一个维度。

四种定义方式:

数据类型 数组名[行数][列数];
数据类型 数组名[行数][列数] = { {数据1, 数据2}, {数据3, 数据4} };
数据类型 数组名[行数][列数] = { 数据1, 数据2, 数据3, 数据4 };
数据类型 数组名[][列数] = { 数据1, 数据2, 数据3, 数据4 };

第二种更加直观,提高代码的可读性。在定义二维数组时,如果初始化了数据,可以省略行数。

二维数组数组名的用途:查看二维数组所占内存空间、获取二维数组首地址。

cout << "二维数组大小: " << sizeof(arr) << endl;
cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;
cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl;
cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
cout << "二维数组首地址:" << arr << endl;
cout << "二维数组第一行地址:" << arr[0] << endl;
cout << "二维数组第二行地址:" << arr[1] << endl;
cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;
cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
  • 二维数组名就是这个数组的首地址
  • 对二维数组名进行 sizeof 时,可以获取整个二维数组占用的内存空间大小

函数

概述

将一段经常使用的代码封装起来,减少重复代码。一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能。

主要有 5 个步骤:返回值类型、函数名、参数表列、函数体语句、return 表达式。

返回值类型 函数名(参数列表)
{
    函数体语句;

    return 表达式;
}
  • 返回值类型:一个函数可以返回一个值,在函数定义中
  • 函数名:给函数起个名称
  • 参数列表:使用该函数时,传入的数据
  • 函数体语句:花括号内的代码,函数内需要执行的语句
  • return 表达式:和返回值类型挂钩,函数执行完后,返回相应的数据

调用

功能:使用定义好的函数。

语法:函数名(参数)

函数定义里小括号内称为形参,函数调用时传入的参数称为实参。

做的是值传递。所谓值传递,就是函数调用时实参将数值传入给形参。值传递时,如果形参发生改变,并不会影响实参。

样式

有 4 种:

// 无参无返
void test01()
{
    // void a = 10; // 无类型不可以创建变量,原因无法分配内存
    cout << "this is test01" << endl;
}

// 有参无返
void test02(int a)
{
    cout << "this is test02" << endl;
    cout << "a = " << a << endl;
}

// 无参有返
int test03()
{
    cout << "this is test03 " << endl;
    return 10;
}

// 有参有返
int test04(int a, int b)
{
    cout << "this is test04 " << endl;
    int sum = a + b;
    return sum;
}

声明

作用:告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。

函数的声明可以多次,但是函数的定义只能有一次。

int max(int a, int b);
int max(int, int);

分文件编写

让代码结构更加清晰。函数分文件编写一般有 4 个步骤:

(1)创建后缀名为 .h 的头文件

(2)创建后缀名为 .cpp 的源文件

(3)在头文件中写函数的声明

(4)在源文件中写函数的定义

swap.h 文件:

#include <iostream>
using namespace std;
// 实现两个数字交换的函数声明
void swap(int a, int b);

swap.cpp 文件:

#include "swap.h"

void swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
}

main 函数文件:

#include "swap.h"

int main()
{
    int a = 100;
    int b = 200;
    swap(a, b);
    system("pause");
    return 0;
}

指针

基本概念

作用:可以通过指针间接访问内存。

  • 内存编号是从 0 开始记录的,一般用十六进制数字表示
  • 可以利用指针变量保存地址

定义和使用

定义语法:数据类型 * 变量名;

int a = 10;
int* p;
p = &a;

cout << &a << endl;
cout << p << endl;
cout << "*p = " << *p << endl;

指针变量和普通变量的区别

普通变量存放的是数据,指针变量存放的是地址。

指针变量可以通过 "*" 操作符操作指针变量指向的内存空间,这个过程称为解引用:

  • 通过 & 符号获取变量的地址
  • 通过 * 符号解引用获得变量值
  • 利用指针可以记录地址
  • 对指针变量解引用,可以操作指针指向的内存

指针所占内存空间

指针也是种数据类型,那么这种数据类型占用多少内存空间?

cpp-image13

所有指针类型在 32 位操作系统下是 4 个字节(X86),在 64 位操作系统下是 8 个字节(X64)。

空指针

指针变量指向内存中编号为 0 的空间。

用途:初始化指针变量(一开始不知道指向哪合适,就初始化等于 NULL)。

注意:空指针指向的内存是不可以访问的。

// 指针变量 p 指向内存地址编号为 0 的空间
int* p = NULL;

// 访问空指针报错
// 内存编号 0~255 为系统占用内存,不允许用户访问
cout << *p << endl;
cpp-image14

野指针

指针变量指向非法的内存空间。

随便给出一个十六进制数,把它强转为地址,用指针变量存放它,理论上是可行的。但这就像是跑到宾馆去开别人的房门,不太道德。为了防止这种不道德的事情发生,编译器会阻止你,告诉你没有权限访问——因为这块内存并不是自己申请来的。

// 指针变量 p 指向内存地址编号为 0x1100 的空间
int* p = (int*)0x1100;

// 访问野指针报错
cout << *p << endl;

空指针和野指针都不是我们申请的空间,因此不要访问。

指针与 const

const 修饰指针有三种情况:const 修饰指针(常量指针)、const 修饰常量(指针常量)、const 既修饰指针又修饰常量。

常量指针 const int* p / int const* p

const 在 * 号前面(修饰 *p 不能改变,但 p 能变):

  • 指针的指向可以修改(可以指向其他地址)
  • 但不可以通过指针去修改所指向地址里的值
const int* p1 = &a;
int const* p2 = &b;

p1 = &c; // 正确
p2 = &c; // 正确
// *p1 = 100;  报错
// *p2 = 100;  报错
指针常量 int* const p

const 在 * 后面,修饰 p(p 放的是地址,意思是地址不能变):

  • 指针指向不可以改,指针指向的值可以更改
  • (只能固定指向某一处,不能指向别处,可以通过 *p 去修改该地址里的值)
int* const p3 = &a;
// p3 = &b;  // 错误
*p3 = 100;   // 正确
const 既修饰指针,又修饰常量

既不可以改指向,又不可以通过 *p 去改里面的值。

const int* const p3 = &a;
// p3 = &b;  // 错误
// *p3 = 100; // 错误

看 const 右侧紧跟着的是什么、它在 * 的前面还是后面,就知道到底是什么不能改。

constexpr
cpp-image15 cpp-image16 cpp-image17

指针与数组

可以利用指针访问数组中元素。

数组其实就可以看做是一个指针常量 type* const p——一旦定义就只能指向这么一块连续的内存单元;数组名是首地址,相当于指针的名字。指针之间是可以赋值的,用这个指针赋值给另一个指针,使得两个指针都指向同一块地方。利用这一点就能实现用指针访问数组中的每一个元素:

int arr[] = { 1,2,3 };
int *p = arr;   // 相当于把一个指针的值赋给另一个指针
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
cout << "第一个元素: " << arr[0] << endl;
cout << "把数组当指针常量访问第一个元素: " << *arr << endl;
cout << "指针访问第一个元素: " << *p << endl;

for (int i = 0; i < 10; i++)
{
    cout << arr[i] << endl;
}

for (int i = 0; i < 10; i++)
{
    cout << *p++ << endl; // 利用指针遍历数组
}

但是不能 arr++,因为它只能指向数组的头。其他方面两者都可以换来换去用,是一样的。

指针和函数

利用指针作函数参数,可以修改实参的值——值传递和地址传递的区别:

原本函数里只是传值,里面的修改对外面没有影响,要做改变得靠返回值。但传地址就不一样了,直接用门牌号让函数直接拿外面的实参进行操作,函数里的操作就是对外面数据本身的操作,因此可以实现修改的功能。

// 值传递
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;
    system("pause");
    return 0;
}

如果不想修改实参,就用值传递;如果想修改实参,就用地址传递。

指针、数组、函数

// 冒泡排序函数
void bubbleSort(int* arr, int len)  // int * arr 也可以写为 int arr[]
{
    for (int i = 0; i < len - 1; i++)
    {
        for (int j = 0; j < len - 1 - i; 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++)
    {
        cout << arr[i] << endl;
    }
}

int main() {
    int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
    int len = sizeof(arr) / sizeof(int);
    bubbleSort(arr, len);
    printArray(arr, len);
    system("pause");
    return 0;
}

当数组名传入到函数作为参数时,被退化为指向首元素的指针。

结构

概念

结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。

定义/使用

struct 结构体名
{
    结构体成员列表
};

struct student
{
    string name;
    int age;
    int score;
};

通过结构体创建变量的方式有三种:

(1) 定义结构体时顺便创建变量:

struct student
{
    string name;
    int age;
    int score;
}stu;

(2) struct 结构体名 变量名

struct student stu;
stu.name = "张三";
stu.age = 18;
stu.score = 100;

(3) struct 结构体名 变量名 = { 成员1值, 成员2值... }

struct student stu = { "李四",19,60 };

输出:

cout << "姓名:" << stu.name << " 年龄:" << stu.age << " 分数:" << stu.score << endl;

在 C++ 中:

  • 定义结构体时的关键字是 struct,不可省略
  • 创建结构体变量时,关键字 struct 可以省略(C 中不能省)
  • 结构体变量利用操作符 "." 访问成员

结构体数组

将自定义的结构体放入到数组中方便维护。

语法:

struct 结构体名 数组名[元素个数] = { {} , {} , ... {} };
struct student
{
    string name;  // 姓名
    int age;      // 年龄
    int score;    // 分数
};

// 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;

相较于 C,C++ 里输出要简单一些。不过输出字符串要头文件 <string>

结构体指针

作用:通过指针访问结构体中的成员。

利用操作符 -> 可以通过结构体指针访问结构体属性。

struct student
{
    string name;
    int age;
    int score;
};

main 中:

student stu = { "张三",18,100 };
student* p = &stu;

p->score = 80; // 指针通过 -> 操作符可以访问成员

cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;

结构体嵌套结构体

结构体中的成员可以是另一个结构体。

// 学生结构体定义
struct student
{
    string name;  // 姓名
    int age;      // 年龄
    int score;    // 分数
};

// 教师结构体定义
struct teacher
{
    int id;       // 职工编号
    string name;  // 教师姓名
    int age;      // 教师年龄
    struct student stu; // 子结构体 学生
};

结构体做函数参数

作用:将结构体作为参数向函数中传递。

传递方式有两种。

值传递:

void printStudent(student stu)
{
    stu.age = 28;
    cout << "子函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
}

// main 中
student stu = { "张三",18,100 };
printStudent(stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;

地址传递:

void printStudent2(student* stu)
{
    stu->age = 28;
    cout << "子函数中 姓名:" << stu->name << " 年龄: " << stu->age << " 分数:" << stu->score << endl;
}

printStudent2(&stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;

同理,一个不被修改、一个被修改。如果不想修改主函数中的数据,用值传递,反之用地址传递。通常情况下是希望改变的,而如果传值就需要进行复制操作,浪费空间和时间,所以不如传指针来得快。

结构体中的 const

用 const 来防止误操作。像写输出函数时,保证不会修改它的值,就能在函数参数表中加上 const。

void printStudent(const student* stu)
{
    // stu->age = 100; // 操作失败,因为加了const修饰
    cout << "姓名:" << stu->name << " 年龄:" << stu->age << " 分数:" << stu->score << endl;
}

const 在参数表时,表示它修饰的东西在该函数内不可被修改。

通讯录管理系统

四个版本的通讯录工程(本机路径存档):

D:\VSC++代码\通讯录管理系统\通讯录管理系统\通讯录管理系统.cpp
D:\VSC++代码\通讯录链表实现\通讯录链表实现\源.cpp
D:\VSC++代码\通讯录_链表_mysql\通讯录_链表_mysql\通讯录_链表_mysql.cpp
D:\EasyX代码\图形化通讯录\图形化通讯录\源.cpp

C11 新特性

别名

typedef 和 using。

cpp-image18

重点:P61。

cpp-image19

要理解"推出",不能直接代入。由 typedef char* pstring; 得 pstring 为 char 的指针。

cpp-image20

要把它作为一个整体:const pstring cstr = 0char* const cstr

cpp-image21

意义是 cstr 为常量指针,它的指向不能改。如果直接代入去看,成了 const char* cstr,那么意义就变成了指向字符常量的指针,意义扭曲了。

so 重点在于:要当做整体,而非代入。

cpp-image22

未知(需推)类型

auto
cpp-image23 cpp-image24
decltype
cpp-image25 cpp-image26 cpp-image27
int i = 42, *p = &i;

decltype(*p) 的推断结果为 int&。注意:解引用得到的是指针所指的对象,并可以对其进行操作,这很符合引用的作用,所以对于得到 int& 的结果可以理解。如果反推的话,若得到的结果是个 int,那就是传值而非指针了,操作不相关了,所以不应该是 int。

另一个问题:decltype((val)) 这种双重括号得到的一定是引用,而 decltype(val) 单重括号得到的是不是引用得根据 val 自身决定。

为什么双重括号一定是引用?把括号内的东西当做表达式去理解:

??理解成变量其实也就是对一块地址取名字,是对该地址的引用?所以这个表达式的结果是引用?以及赋值是会产生引用的一类经典表达式,引用的类型就是左值的类型,这个是否也是这样理解?所以其实变量的一切都是引用,引用的某个内存空间,然后对于变量的引用又是一个引用……???

这一切都和左值相关:左值返回的是引用类型。双重括号 (variable) 将变量视为一个表达式,这个表达式的结果类型是 variable 的类型作为引用——因为双重括号强制将 variable 视为一个左值表达式,而左值表达式的 decltype 结果是引用类型,以便可以修改它们的值。

同样适用于"赋值是会产生引用的一类经典表达式,引用的类型就是左值的类型":赋值表达式的结果就是左操作数本身(左值),其引用类型与左值的类型相同——赋值操作需要将右边的值赋给左边的变量,左值可以被修改,因此结果是一个引用以允许对左值进行修改。

所以可以这样:

int a = 3, b = 4;
decltype((a = b)) result = a; // result 是 int& 类型

对照三种推导:

int a = 3, b = 4;
decltype(a) x = a;        // x 的类型是 int(a 是变量名,取声明类型)
decltype(a = b) y = a;    // y 的类型是 int&(赋值表达式的结果就是左操作数,是左值)
decltype((a = b)) z = a;  // z 的类型是 int&(加上括号仍是同一个左值)

总之,decltype 的推导结果取决于表达式的左值或右值属性:变量名(不带括号)取声明类型;带括号或运算的表达式按值类别推导,左值得引用、右值得类型本身。

关于头文件 name.h or cname

C 中的头文件在 C++ 中兼容。

cpp-image28

附件

📄 C++.docx(原 word 笔记):C++.docx


⬅️ C++ 🏠 00-编程语言 ➡️ C++ 核心