Hello World

#include <iostream>
#include "windows.h"

using namespace std;

int main(void){
    int a;
    int b;

    cout<<"Please input a and b:\n";
    cin>>a;
    cin>>b;

    cout<<"a+b=";
    cout<<a+b;

    system("pause");
    return 0;
}

在 VS Code 中运行 C++ 代码会出现控制台窗口闪退的问题

在代码中加入 #include "windows.h"system("pause"); 可以解决

iostream 与 iostream.h

#include <iostream.h> 是非标准输入输出流,标准化前的头文件

#include <iostream> 是标准输入输出流,标准化后的头文件,需要配合命名空间使用 using namespace std;

基本数据和表达式

关键字

auto    break    case    char    class    const    continue    default    delete    else
enum    explicit    extern    float    for    friend    goto    if    inline    int    long 
new    operator   private    protected    public    register    return    short    signed 
sizeof    static    struct    switch    this    typedef    union    unsigned    virtual    
void    while

标识符

字母、下划线开始,由字母、下划线、数字组成的字符串

  • 不能使用关键字
  • 区分大小写
  • 长度无规定

合法:

a    x1    no_1    _a2c    sum    Name    name

非法:

2a    x+y    a,b    a&b    const 

数据类型

image-20230303214001084

数据类型长度

printf("%d\n",sizeof(byte));
printf("%d\n",sizeof(short));
printf("%d\n",sizeof(int));
printf("%d\n",sizeof(long));
printf("%d\n",sizeof(float));
printf("%d\n",sizeof(double));
printf("%d\n",sizeof(boolean));
printf("%d\n",sizeof(char));

printf("\n");

printf("%d\n",sizeof(long int));
printf("%d\n",sizeof(long long));
1
2
4
4
4
8
1
1

4
8
byte short int long float double boolean char
1 2 4 4 4 8 1 1
long int long long
4 8

int

十进制(Decimal, base 10)

image-20230303214114783

八进制(Octal, Base 8)

image-20230303214330756

image-20230303214353443

十六进制(Hexadecimal, base 16)

image-20230303214439192

image-20230303214458727

bool

只有两个值:truefalse

enum

枚举类型,用标识符代替在列表中的序号

#include <iostream>
#include "windows.h"

using namespace std;

enum color{red, blue, green};

int main(void){
    color c;

    c = green;
    cout << c;

    system("pause");
    return 0;
}
2

red = 0

blue = 1

green = 2

浮点型(float, double, long double)

image-20230303215237667

常用示数方式

image-20230303215255719

image-20230303215313162

科学示数方式

image-20230303215552595

char

image-20230303215636357

image-20230303215700154

转义字符

image-20230303215735835

数据对象和访问

程序使用内存单元存放数据,且可以对内存单元进行命名(标识符)

对内存的读、写称为访问

既能读,又能写内存对象称为变量

一旦初始化就不能修改的内存对象称为常量

变量

变量是存储单元

变量定义:申请指定类型的存储空间,并以指定标识符命名

访问变量

内存单元由操作系统按字节(Byte)编号,称为地址

一个对象占有内存的第一个字节的地址称为对象的地址

可以通过对象名地址访问对象

数据对象有两种访问形式:读、写

地址访问

程序编译后,系统对已声明对象生成一张名表,登记对象的属性

<名字,类型,地址>

image-20230304200316406

image-20230304200615232

C++ 允许通过名或地址访问对象

间址运算符

以下两种方式是等效的:a == *(&a)

image-20230304200743175

指针变量

能够存放对象地址的变量

  • & 取地址
  • * 间址访问

下图,a == *p1b == *p2,两者等效

image-20230304201052926

image-20230304201227624

区别 int *p 和 *p

前者是指针定义,后者是指针所指向的变量

区别 int 和 int *

若编译以下代码则会报错:

int i = 1;
int *p;

p = i;
不能将 "int" 类型的值分配到 "int *" 类型的实体

即类型不匹配,intint * 不属于同种类型

输出 p 和 *p
int i = 1;
int *p = &i;

cout << p; //输出存放的地址
0x61ff08
int i = 1;
int *p = &i;

cout << *p; //输出指向变量的值
1
交换指针存储的地址
int a = 1;
int b = 2;

int *p1 = &a;
int *p2 = &b;

int *p;
p = p1;
p1 = p2;
p2 = p;

cout << *p1;
2

image-20230304203831437

交换指针所指变量的值
int a = 1;
int b = 2;

int *p1 = &a;
int *p2 = &b;

int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;

cout << a;
2

image-20230304203816465

空指针

以下两种方式均可以创建空指针:

int *p = 0;
int *p = null;
指向指针的指针
int **pp;
int *p;
int i = 1;

p = &i;
pp = &p;

cout << p << endl;
cout << *p << endl;

cout << pp << endl;
cout << *pp << endl;
cout << **pp << endl;
0x61ff04 // 指针 p 存放的地址,即 i 的地址
1 // *p 

0x61ff08 // 指针 pp 存放的地址,即指针 p 自身的地址 
0x61ff04 // 指针 *pp 存放的地址,即指针 p 存放的地址,即 i 的地址
1 // **pp

image-20230304220611132

指针类型转换
int *intPointer;
char *charPointer;

int i = 65;
intPointer = &i;

charPointer = (char *)intPointer;

cout << *charPointer << endl;
A

ascii 码表中,十进制 65 对应的字符为 A

引用

int i;
int *p;
int &iRefer = i;

i = 1;
p = &i;

cout << i << endl;
cout << *p << endl;
cout << iRefer << endl;
1
1
1

iRefer 相当于 i 的别名,且必须在定义时初始化

i == *p == iRefer,三者等效

注意区别 int &&i,前者是引用,后者是取地址符

常量

关键字 const 可以约束访问对象的权限,使其只能读,不能写,不允许修改对象的值

const double PI = 3.14;
const int MAX = 666;
常量指针

const 在星号之前

const 类型 * 指针名

类型 const * 指针名

常量指针,既可以指向常量,也可以指向变量,但是若指向变量,则不能修改指向的变量值

const int i = 114514;
int j = 1919;

const int *p = &i;
p = &j;

//报错,不能通过常量指针修改变量值
//*p = 666;

cout << *p << endl;
1919
指针常量

const 在星号之后,注意与常量指针区分

类型 * const 指针名

指针常量,其中保存的地址不可改变,即不可再指向别的变量

int i = 114514;
int j = 1919;

int * const p = &i;

//报错,不可修改指针保存的地址
//p = &j;

cout << *p << endl;
指向常量的指针常量

星号两边都有 const

const 类型 * const 指针名

指向常量的指针常量:

  • 指针保存的地址不可修改
  • 指针指向的常量值不可修改
const int i = 114514;
const int * const p = &i;

cout << *p << endl;
常引用

const 类型 & 引用名

int i = 114514;
const int & refer = i;

//报错,不能通过常引用修改变量值
//refer = 1919;

cout << refer << endl;

表达式

由数据和运算符,按一定规则,表达某个值的式子

运算符

image-20230305212820289

运算符可分为:

  • 单目运算符:运算符 右操作数

    -123 +500

  • 双目运算符:左操作数 运算符 右操作数

    a + 1 x > y

  • 三目运算符:操作数1 ? 操作数2 : 操作数3

    a ? b : c

运算符优先级

image-20230305212844044

运算符多义性
int a = 1;

// 引用说明符
int &ra = a;

// 指针说明符 取址运算符
int *p = &a;

// 算术乘法
a = ra * 6;

// 间址访问 算术乘法
*p = 5 * *p; // 等同于 *p = 5 * (*p)

cout << *p << endl;
30
C++ 取余运算 %

c++ 中 % 运算符为取余,而不是取模

a % b

  • 计算整数商:c = a / b(向 0 方向取整,如 1.5 取 1,-1.5 取 -1)
  • 计算余数:r = a - c * b

数据输入和输出

c++ 的输入输出操作由 IO 流库提供

cincout 是 IO 流库定义的两个标准对象,在 iostream.h 头文件中声明

输入

cin >> 变量1 >> 变量2 >> 变量3 >> ...

这里的 >>提取运算符,不是位运算符

输出

cout << 表达式1 << 表达式2 << 表达式3 << ...

这里的 <<插入运算符,不是位运算符

image-20230305214343257

输出格式控制符

image-20230305214406792

程序控制结构

选择控制

if 语句

switch 语句

switch(表达式){
    case 常量表达式:
       ...
       break; 
    case 常量表达式:
       ...
       break; 
  
    default:
       ...
}
  • 表达式必须为整型、枚举类型,不能为浮点型
  • 常量表达式必须是常量,且与 switch 括号中的变量类型一致
  • default 可选

循环控制

while 语句

do while 语句

for 语句

for(表达式1;表达式2;表达式3){
    ...
}

表达式 1、表达式 2、表达式 3 均可省略

转向控制

break 语句

continue 语句

goto 语句

函数

  • 函数(Function)是功能抽象模块
  • 函数的作用:任务划分、代码重用

函数定义

由两部分组成:函数头、函数体

返回值类型 函数名( 参数列表 ){
   ...
}

int getMax(int x, int y){
    if(x > y){
        return x;
    }else {
        return y;
    }
}

函数参数传递

值传递

把传入参数(实际参数)的值复制给函数的形式参数,在这种情况下,修改函数内部的形参不会改变实参

// 函数定义
void swap(int x, int y)
{
   int temp;
 
   temp = x; /* 保存 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y */
  
   return;
}
int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;
 
   // 调用函数来交换值
   swap(a, b);
 
   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 100
交换后,b 的值: 200

指针传递

把传入参数的地址复制给形式参数,修改形参会影响实参

// 函数定义
void swap(int *x, int *y)
{
   int temp;
   temp = *x;    /* 保存地址 x 的值 */
   *x = *y;        /* 把 y 赋值给 x */
   *y = temp;    /* 把 x 赋值给 y */
  
   return;
}
int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;

   /* 调用函数来交换值
    * &a 表示指向 a 的指针,即变量 a 的地址 
    * &b 表示指向 b 的指针,即变量 b 的地址 
    */
   swap(&a, &b);

   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100

引用传递

把传入参数的引用的地址复制给形参,在这种情况下,形参就是实参的别名,修改形参会影响实参

// 函数定义
void swap(int &x, int &y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */
  
   return;
}
int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;
 
   /* 调用函数来交换值 */
   swap(a, b);
 
   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100

递归调用

  • 递归形式(算法)
  • 修改条件(缩小问题规模)
  • 终止条件
// 求阶乘 n!
int F(int n){
    if(n == 0){ // 终止条件
        return 1;
    }else {
        return n * F(n - 1); // 修改条件 n = n - 1;
    }
}
// 求斐波那契数列第 n 项
int F(int n){
    if(n <= 2){
        return 1;
    }else {
        return F(n - 1) + F(n - 2);
    }
}

函数指针

  • 每个函数都有一个首地址,称为函数的入口地址(函数指针)
  • 不带括号的函数名就是函数入口地址
  • 函数调用:找到函数入口地址、传递参数

image-20230306152736611

函数的地址

void hello(){
    cout << "hello" << endl;
}

int main(void){
    // 以下三种方式等效,都能调用 hello 函数
    hello();
    (& hello)();
    (*& hello)();

    cout << hello << endl; // 函数名是地址
    cout << (& hello) << endl; // 取函数地址
    cout << (*& hello) << endl; // 函数地址所指对象

    system("pause");
    return 0;
}

注意,& hello*& hello 都会被解析为函数

函数的指针

  • 指向函数的指针称为函数指针
  • 函数的类型是函数的接口
  • 可以通过指针变量的间址方式调用函数
函数类型
// 以下为相同类型的函数
int max(int x, int y);
int min(int x, int y);
int avg(int x, int y);

它们的类型为 int (int, int)

定义函数类型
typedef 返回值类型 函数类型名 (参数列表)

typedef int myFunctionType(int, int);
定义指针变量
int (*fp)(int, int);

//或
myFunctionType *fp;
#include <iostream>
#include "windows.h"

using namespace std;

int max(int x, int y);
int min(int x, int y);
int avg(int x, int y);

int main(void){
    int (*fp) (int, int);

    fp = max;
    cout << fp(1,3) << endl;

    fp = min;
    cout << fp(1,3) << endl;

    fp = avg;
    cout << fp(1,3) << endl;

    system("pause");
    return 0;
}

int max(int x, int y){
	...
}
int min(int x, int y){
	...
}
int avg(int x, int y){
    ...
}
#include <iostream>
#include "windows.h"

using namespace std;

int max(int x, int y);
int min(int x, int y);
int avg(int x, int y);

typedef int myIntType(int, int);

int main(void){
    myIntType *fp1, *fp2, *fp3;

    fp1 = max;
    fp2 = min;
    fp3 = avg;

    cout << fp1(1,3) << endl;
    cout << fp2(1,3) << endl;
    cout << fp3(1,3) << endl;

    system("pause");
    return 0;
}

int max(int x, int y){
	...
}
int min(int x, int y){
	...
}
int avg(int x, int y){
    ...
}

注意,对于第一个程序 fp = maxfp 存放的是函数的地址,max 是函数的直接地址

所以 fp 就是 max&max),而 &fp 不等于 max&max

内联函数

内联函数是 C++ 为降低小规模函数的调用开销的一种机制

这种机制是编译器在编译时,将内联函数的调用以相应代码代替

内联函数声明:inline 函数名()

inline int hello();

int main(void){
	...
}

int hello(){
    ...
}
inline int hello(){
    ...
}

int main(void){
	...
}

错误示例:

//重复说明,语法错误
inline int hello();

int main(void){
	...
}

inline int hello(){
    ...
}
//视为普通函数,inline 关键字无效
int hello();

int main(void){

}

inline int hello(){
    ...
}

函数重载

多个函数:

  • 参数个数相同,但类型不同、返回值类型不同
  • 参数个数不同、返回值类型不同

上述两种情况均可实现函数重载

另外注意:

仅返回值类型不同,属于语法错误,编译器无法确定唯一函数(函数重定义)

程序内存区域

image-20230306210024111

自动存储类

image-20230306210117065

静态存储类

  • 关键字 externstatic 声明静态存储变量和函数
  • extern 声明全局,static 声明局部
  • 两者说明变量时,程序开始执行时,就会分配和初始化内存空间,静态变量默认初始值为 0
  • 两者说明函数时,程序开始执行时,就存在这个函数

标识符作用域

函数原型作用域

函数原型形式参数列表中的标识符具有函数原型作用域

int fun(int, int);

int fun(int a, int b);

int fun(int x, int y);

三种函数原型,编译器视为相同

块作用域

在语句块中声明的标识符具有块作用域

int main(void){
    int a = 111;
    {
        int a = 222;// 内层的 a 覆盖了外层的 a
        cout << a << endl;
    }
    cout << a << endl;

    return 0;
}
222
111

函数作用域

语句标号(后面带冒号的标识符)是唯一具有函数作用域的标识符

switchcase 标号

文件作用域

任何在函数之外声明的标识符具有文件作用域

这种标识符从声明处到文件结尾,其中任何函数都可见

全局变量和局部变量

  • 全局变量:文件作用域
  • 局部变量:函数作用域、块作用域

全局变量声明时默认初始值为 0

当局部变量和全局变量同名时,局部变量在块内会覆盖全局变量

若要在块内访问全局变量,则需要域运算符 ::

int x;

int main(void){
    int x = 666;

    cout << x << endl;
    cout << ::x << endl;

    system("pause");
    return 0;
}
666
0

预处理指令

C++ 编译器工作过程

image-20230306211626104

  • 预处理器:改善程序的组织管理
  • 预处理指令:所有编译指令以 # 开头,每条指令单独占一行

文件包含

include 指令在编译之前,把指定文件包含到该命令所在位置

#include <文件名>

#include "文件名"

条件编译

#if 常量表达式
程序段
#endif
#if 常量表达式
程序段1
#else
程序段2
#endif
#if 常量表达式1
程序段1
#elif 常量表达式2
程序段2
...
#elif 常量表达式n
程序段n
#else
程序段n+1
#endif

宏定义

用指定正文替换程序中出现的标识符

#define 标识符 文本

分号可加可不加

不带参宏定义
#define PI 3.14
带参宏定义
#define getArea(r) PI * r * r

命名空间

  • 命名空间是类、函数、对象、类型和其他名字的集合
  • 命名空间可以让程序组件之间不会产生命名冲突
  • std 是 C++ 标准名空间

标准名空间

C++ 标准头文件没有扩展名 .h

iostream  iomanip  limit  fstream  string  typeinfo  stdexcept

使用标准类库的组件时,需要指定名空间

C++ 标准名空间 std

使用标准名空间

#include<iostream>

using namespace std;

int main(){ 
    int a, b;
    cin >> a;			
    cin >> b;			
    cout << a+b << endl; 	
}
#include<iostream>

using std::cin; 
using std::cout;

int main(){ 
    int a, b;
	cin >> a;	
	cin >> b;	
	cout << a+b << endl; 
}
#include<iostream>

int main(){ 
    int a, b;
	std::cin >> a;
	std::cin >> b;
	std::cout << a+b << std::endl; 
}

数组

  • 数组是由一定数量的同类元素,按顺序排列而成的数据结构
  • 一个数组在内存中占有一块连续的内存区域
  • 数组名就是数组首元素的地址
  • 数组每个元素由下标进行表示

一维数组

元素是基本类型、结构类型或类

一维数组也可视为向量

定义

类型 标识符[表达式]

int A[10];
char B[MaxSize];
double C[m*n];

初始化

// 为数组赋初值
int a[5] = {1,2,3,4,5};

// 全部初始化为0
int b[10] = {0};

// 第0、1、2号元素赋初值,其他为0
int c[10] = {1,2,3};

// 自动推断数组长度为5
int d[] = {1,2,3,4,5}

下标访问

数组名 [表达式]

指针访问

int a[5] = {1,2,3,4,5};

// 数组名是首元素地址
a == &a[0]
a+1 == &a[1]

// 指针访问
*a == a[0]
*(a+1) == a[1]

指针数组

元素类型为指针的数组

每个指针元素都存放了某个地址

类型 * 标识符[表达式]

指向基本元素的指针数组

int a = 1;
int b = 2;
int c = 3;

int * pArr[3];

pArr[0] = &a;
pArr[1] = &b;
pArr[2] = &c;

cout << *pArr[0] << endl;
cout << *pArr[1] << endl;
cout << *pArr[2] << endl;
1
2
3

指向数组的指针数组

int a[2] = {1,2};
int b[2] = {3,4};
int c[2] = {5,6};

int (*pArr[3])[2];

pArr[0] = &a;
pArr[1] = &b;
pArr[2] = &c;

for(int i = 0; i < 3; i++){
    for(int j = 0; j < 2; j++){
        cout << *(*pArr[i] + j) << endl;
    }
}

关于本例中 a&a

a 作为一个数组名,本身就是首元素地址

&a 没有实际意义,与 a 等效,首元素地址

但是注意:

pArr[] 数组元素(指针)指向一维数组,其元素类型为二级指针

a一级指针,若写成 pArr[0] = a 则会报错

所以需要写成pArr[0] = &a,两边逻辑上都是二级指针

指向函数的指针数组

#include <iostream>
#include "windows.h"

using namespace std;

const double PI = 3.14;

double getArea(double r){
    return PI * r * r;
}

double getGirth(double r){
    return 2 * PI * r;
}

// 定义函数类型
typedef double circleFunction(double);

int main(void){
    // 利用函数类型,定义函数指针
    circleFunction * pArr[2];

    pArr[0] = getArea;
    pArr[1] = getGirth;

    cout << (*pArr[0])(3.0) << endl;
    cout << (*pArr[1])(3.0) << endl;

    system("pause");
    return 0;
}
28.26
18.84

二维数组

元素类型为一维数组的数组

其中的每个一维数组元素类型相同长度相同

下标访问

数组名[表达式1][表达式2]

指针访问

int a[3][5];

二维数组名 a逻辑上的二级指针,a[i] 是一级指针

但是,二维数组名不能直接赋给二级指针变量,因为无论 n 维数组,数组名都是首元素地址

int *p1;
int **p2;

// 正确
p1 = *a;
p1 = a[0];

// 错误
p1 = a;
p2 = a;
求元素地址
// 第0行第1列的元素地址
a[0] + 1
*a + 1
&a[0][1]
// 第3行第4列的元素地址
a[3] + 4
*(a + 3) + 4
&a[3][4]
// 第i行第j列的元素地址
a[i] + j
*(a + i) + j
&a[i][j]
求元素的值
// 第1行第2列元素的值
*(a[1] + 2)
*(*(a + 1) + 2)
a[1][2]
// 第i行第j列元素的值
*(a[i] + j)
*(*(a + i) + j)
a[i][j]
应用
int a[3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int *p;

for(p = a[0]; p < a[0] + 15; p++){
    cout << *p << endl;
}

for(p = *a; p < *a + 15; p++){
    cout << *p << endl;
}

for(int i = 0; i < 3; i++){
    for(int j = 0; j < 5; j++){
        cout << *(a[i] + j) << endl;
    }
}
  • 可以改为 p = a 吗?

    NO.

    a 是逻辑上的二级指针,不能赋给一级指针变量 p

  • 可以改为 p = a[0][0] 吗?

    NO.

    a[0][0] 是元素值

  • 可以改为 p = &a[0][0] 吗?

    YES.

一维数组作为函数参数

  • 数组作为参数,与一般变量相同
  • 数组名作为参数时,传递首元素地址
int sumArr(int arr[], int len){
    int sum = 0;

    // arr 本质是一个指针
    for(int i = 0; i < len; i++){
        sum += *arr;
        arr++;
    }

    return sum;
}

int main(void){
    int a[3] = {1, 2, 3};

    cout << sumArr(a, 3) << endl;

    system("pause");
    return 0;
}
6

形式参数 int arr[] 本质上就是一个普通指针,等价于 int * arr

也可以换成:

for(int i = 0; i < len; i++){
    sum += arr[i];
}

二维数组作为函数参数

以下是常见错误:

#include <stdio.h>

void foo(int a[][], int m, int n) {
    int i = 1;
    int j = 1;
    printf("a[%d][%d]=%d\n", i, j, a[i][j]);
}

int main() {
    int a[2][3] = {
        {1,2,3},
        {4,5,6}
    };
    foo(a, 2, 3);
}

报错:

$ gcc test.c
test.c:3:14: error: array type has incomplete element type ‘int[]’
 void foo(int p[][], int m, int n) {
              ^
test.c: In function ‘main’:
test.c:12:9: error: type of formal parameter 1 is incomplete
     foo(a, 2, 3);

报错原因:

C 语言对二维数组的存储是按一维数组来处理的,按照行展开,然后顺序存储

int a[2][3] = {
        {1,2,3},
        {4,5,6}
};
int b[6] = {
        {1,2,3,4,5,6}
};

两者在内存中的存储顺序是一致的

---+---+--+---+--+--+-------
a: | 1 | 2 | 3 | 4 | 5 | 6
b: | 1 | 2 | 3 | 4 | 5 | 6
---+---+--+---+--+--+-------

所以在利用二维数组作为参数传递时,必须指定二维数组的列数,行数可写可不写,如下所示

void foo(int a[][3], int m, int n) {
    int i = 1;
    int j = 1;
    printf("a[%d][%d]=%d\n", i, j, a[i][j]);
}

动态存储

C++ 的动态存储分配机制可以在程序运行时建立和撤销对象

new 操作符

动态分配堆内存

指针变量 = new 类型 (常量),常量可缺省

指针变量 = new 类型 [表达式]

从堆内存分配一块该类型大小的存储空间,并返回首地址

delete 操作符

释放已分配的内存空间

delete 指针变量

delete [] 指针变量

其中指针变量必须是 new 返回的

应用

int * p1 = new int;
char * p2 = new char;
double * p3 = new double;
int * p4 = new int[4];

delete p1;
delete p2;
delete p3;
delete [] p4;
int * p = NULL;
p = new int (114514); // 该int无名,只能通过p指针访问

字符数组与字符串

C++ 没有字符串类型,以字符数组作为字符串

'\0' 是字符串结束的标志,ascii 码为 0

字符数组作为字符串初始化时,自动添加'\0'

用字符数组存放字符串

char s1[10] = {'s', 't', 'u', 'd', 'e', 'n', 't'};
char s2[10] = {"student"};
char s3[] = {"student"};
char s4[] = "student";

用字符指针管理字符串

char *s = "student";

字符串的访问

iostream 对字符串的扩展功能:

  • 字符串常量、字符数组名、字符指针都可以表示字符串
  • 输出字符指针就是输出整个字符串
  • 数组字符指针的间接引用是输出单个字符
char * s = "student";

cout << s << endl; // 输出 student
cout << *s << endl; // 输出 s
char s[3] = {'a','b','c'};
cout << s << endl; // 输出 abc?@ 后两个为无意义字符,因为找不到 '\0'
static char s[3] = {'a','b','c'};
cout << s << endl; // 正确输出 abc 因为静态数组自动对剩余元素初始化为 '\0'
char s[] = "abc";
cout << s << endl; // 正确输出 abc

输入字符串

char s[10]; // 数组长度不能省略,否则报错
cin >> s;
cout << s << endl;
in: abc
out: abc

特例

char *s = "student";

for(int i = 0; i < 7; i++){
    cout << *(s+i) << endl;
}
s
t
u
d
e
n
t
char *s = "student";

for(int i = 0; i < 7; i++){
    cout << (s+i) << endl;
}
student
tudent
udent
dent
ent
nt
t

字符串函数

image-20230307171831248

结构

  • 结构由固定数目的成员组成
  • 各成员可以是不同的数据类型
  • 一个结构变量整体,在内存中占有连续的一块内存空间

定义结构

struct 结构名 {
    类型 成员1;
    类型 成员2;
    ...
};

定义结构及变量

// 声明类型之后,声明变量
struct student {
    char name[10];
    int age;
};

student s1;
student s2;
...
// 声明类型同时,声明变量
struct student {
    char name[10];
    int age;
}s1, s2, *Stu;
// 直接声明变量,且无结构名
struct {
    char name[10];
    int age;
}s1, s2, *Stu;
// 一个结构,可以是另一结构的成员类型
struct date {
    int year;
    int month;
    int day;
};

struct student {
    char name[10];
    int age;
    date birthday;
}s1, s2;

// 报错,成员类型不能是自身所在的结构类型,即不能是递归结构
struct person {
    person father;
    person mother;
}
// 声明结构的同时,可以初始化结构变量
struct student {
    char name[10];
    int age;
}s1 = {"alice", 18};

访问结构

结构变量.成员

s1.name
s1.age

指针访问

结构指针->成员

(*结构指针).成员

方式一
#include <iostream>
#include "windows.h"
#include <cstring> // 注意 strcpy() 需要加上文件头

using namespace std;

struct student {
    char name[10];
    int age;
};

int main(void){
    student s1;
    student *stuPointer;

    stuPointer = &s1;

    strcpy(stuPointer->name, "alice"); 
    stuPointer->age = 18;

    cout << stuPointer->name << endl;
    cout << stuPointer->age << endl;

    system("pause");
    return 0;
}
alice
18

注意,这里对 name[] 数组赋值必须使用 strcpy() 函数

写成 stuPointer->name = "alice" 则会报错

incompatible types in assignment of 'const char [6]' to 'char [10]'
方式二
#include <iostream>
#include "windows.h"
#include <cstring>

using namespace std;

struct student {
    char name[10];
    int age;
};

int main(void){
    student s1;
    student *stuPointer;

    stuPointer = &s1;

    strcpy((*stuPointer).name, "alice");
    (*stuPointer).age = 18;

    cout << stuPointer->name << endl;
    cout << stuPointer->age << endl;

    system("pause");
    return 0;
}
类型相同的结构变量可以相互赋值
#include <iostream>
#include "windows.h"
#include <cstring>

using namespace std;

struct student {
    char name[10];
    int age;
};

int main(void){
    student s1;
    student s2;

    strcpy(s1.name, "alice");
    s1.age = 18;

    s2 = s1;

    cout << s2.name << endl;
    cout << s2.age << endl;

    system("pause");
    return 0;
}
alice
18

结构数组

#include <iostream>
#include "windows.h"
#include <cstring>

using namespace std;

struct student {
    char name[10];
    int age;
};

int main(void){
    student stuArr[3];

    strcpy(stuArr[0].name, "alice");
    stuArr[0].age = 18;

    strcpy(stuArr[1].name, "bob");
    stuArr[1].age = 20;

    strcpy(stuArr[2].name, "clever");
    stuArr[2].age = 22;

    for(int i = 0; i < 3; i++){
        cout << stuArr[i].name << endl;
        cout << stuArr[i].age << endl;
    }

    system("pause");
    return 0;
}
alice
18
bob
20
clever
22

结构内存对齐原则

先看一个例子

struct s1 {
    char c1;
    int i;
    char c2;
};

这个结构体所占用的内存空间大小是 12 字节,而不是 6 字节77

为什么?

因为 c 语言中,结构体需要遵循内存对齐原则

c264ec88a608e28d18ce5897d2c31dfd.png

简单来说,对齐数就是取 <变量大小, 8字节> 两者之间的较小者

如 char 变量对齐数是 1

int 变量对齐数是 4

一个占用 16 字节内存空间的结构体变量(嵌套),对齐数是 8

9d7e61342fe649ed67e60ebafb5d81d8.png

练习

struct s2 {
    char c1;
    char c2;
    int i;
};

s1、s2 的区别仅是变量 i、c2 互换了位置

这个结构体的大小却变成了 8

image-20230310155213224

struct s3 {
    double d;
    char c;
    int i;
};

s3 结构体的大小为 16

struct s3 {
    double d;
    char c;
    int i;
};

struct s4 {
    char c1; // 放在 0 号位置
    struct s3 s; //  放在 8 号位置
    double d; // 放在 24 号位置
}

结构体 s4 的大小为 32

<结构体s3的大小==16, 8> 之间取小者,对齐数为 8

所以 c1 位于 0,s3 位于 8, d 位于 24(8号+16字节)

为什么需要内存对齐?

观点一:可移植性

不是所有的硬件平台都能访问任意地址上的任意数据的

某些硬件平台只能在某些地址处取数据,否则抛出硬件异常

观点二:性能

数据结构(尤其是栈)应该尽可能地在自然边界上对齐

原因在于:

若访问未对齐的内存,处理器需要作两次内存访问

若访问对齐的内存,仅需要一次访问

36f4ddc454649072dfe2d90affd742e4.png