C++学习
使用变量
1 | int main() { |
字符串string类型要使用双引号"",字符类型char要使用单引号''
使用常量
1.宏常量
一般在main函数的上面声明,用大写命名。
语法:#define 常量名 值
2.const修饰的变量
在程序的任何地方都可以声明。
语法:const 数据类型 常量名=值
1 |
|
标识符的命名
1.C++命名规则
C++规定给标识符(变量、常量、函数、结构体、类等)命名时,必须遵守以下规则。
- 在名称中只能使用字母字符、数字和下划线
- 名称的第一个字符不能是数字
- 名称区分大写字符与小写字符
- 不能将C++关键字用作名称
- 以下划线和大写字母打头的名称被保留给编译器及其使用的资源使用,如果违反了这一规则,会导致行为的不确定性。
- C++对名称的长度没有限制,但有些平台可能有长度限制(64字符)。
C++提倡有一定含义的名称(望名知义)
2.C++关键字
关键字也叫保留字,是C++预先保留的标识符。
每个C++关键字都有特殊的含义,用于声明类型、对象、函数、命名空间等,程序中不能声明与关键字同名的标识符。
| asm | do | if | return | typedef |
|---|---|---|---|---|
| auto | ==double== | inline | short | typeid |
| ==bool== | dynamic_cast | ==int== | signed | typename |
| break | else | long | sizeof | union |
| case | enum | mutable | static | unsigned |
| catch | explicit | ==namespace== | static_cast | ==using== |
| ==char== | export | new | struct | virtual |
| class | extern | operator | switch | void |
| ==const== | ==false== | private | template | volatile |
| const_cast | ==float== | protected | this | wchar_t |
| continue | for | public | throw | while |
| default | friend | register | ==true== | |
| delete | goto | reinterpret_cast | try |
输入数据
程序输入数据的方式有多种。
- 从控制台的界面中输入(网页、PC桌面程序、APP程序)
- 从文件中读取
- 从数据库中读取
- 从网络中读取
1.用std::cin输入数据
语法:std::cin>>变量名
注意:
- 布尔型变量的值在计算机内部用1(true)和0(false)存储;程序中可以书写true和false,也可以书写1和0,其它值将强制转换成1;用cin输入时可以填1和0,其它值也强制转换成1;用cout输出时只显示1和0,不显示true和false
- 如果输入的数据与变量的数据类型不匹配,会导致行为的不确定性
1 |
|
逗号运算
1.逗号运算
把一行语句中的多个表达式连接起来,程序将从左到右执行表达式。
语法:表达式一, 表达式二, ……, 表达式n;
逗号运算常用于声明多个变量
1 | int a,b; // 声明变量a和b。 |
也可以用于其它语句中,但是,逗号运算符是所有运算符中级别最低的,以下两个表达式的效果是不同的。
1 | int a,b; |
1 |
|
1 | a=2 |
运算的优先级
1.运算的优先级
一个表达式可以包含多个运算符,运算符的优先级决定了表达式各部分的执行顺序。
例如,按照运算规则, 的优先级比+高,所以的b c将先执行:
1 | a + b * c; |
如果想让a + b先执行,则必须使用括号:
1 | (a + b) * c; |
如果一个表达式中操作符的优先级相同,那么它们的结合律(associativity)决定了它们的执行顺序(从左到右或从右到左)。
例如,算术运算的组合方式是从左到右,赋值运算则是从右到左。如下:
| 表达式 | 结合律 | 组合方式 |
|---|---|---|
| a/b%c | 从左到右 | (a/b)%c |
| a=b=c | 从右到左 | a=(b=c) |
下表是全部运算符的优先级和结合律:
| 优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 |
|---|---|---|---|---|
| 1 | [] | 下标 | 地址[表达式] | 左到右 |
| 1 | () | 圆括号 | (表达式)/函数名(形参表) | 左到右 |
| 1 | . | 成员选择(对象) | 对象.成员名 | 左到右 |
| 1 | -> | 成员选择(指针) | 对象指针->成员名 | 左到右 |
| 2 | - | 负号运算符 | -表达式 | 右到左 |
| 2 | (类型) | 强制类型转换 | (数据类型)表达式 | 右到左 |
| 2 | ++ | 前置自增运算符 | ++变量名 | 右到左 |
| 2 | ++ | 后置自增运算符 | 变量名++ | 右到左 |
| 2 | -- | 前置自减运算符 | --变量名 | 右到左 |
| 2 | -- | 后置自减运算符 | 变量名-- | 右到左 |
| 2 | * | 取值运算符 | *指针变量 | 右到左 |
| 2 | & | 取地址运算符 | &变量名 | 右到左 |
| 2 | ! | 逻辑非运算符 | !表达式 | 右到左 |
| 2 | ~ | 按位取反运算符 | ~表达式 | 右到左 |
| 2 | sizeof | 长度运算符 | sizeof(表达式) | 右到左 |
| 3 | / | 除 | 表达式/表达式 | 左到右 |
| 3 | * | 乘 | 表达式*表达式 | 左到右 |
| 3 | % | 余数(取模) | 整型表达式/整型表达式 | 左到右 |
| 4 | + | 加 | 表达式+表达式 | 左到右 |
| 4 | - | 减 | 表达式-表达式 | 左到右 |
| 5 | << | 左移 | 变量 | 左到右 |
| 5 | >> | 右移 | 变量>>表达式 | 左到右 |
| 6 | > | 大于 | 表达式>表达式 | 左到右 |
| 6 | >= | 大于等于 | 表达式>=表达式 | 左到右 |
| 6 | < | 小于 | 表达式 | 左到右 |
| 6 | <= | 小于等于 | 表达式 | 左到右 |
| 7 | == | 等于 | 表达式==表达式 | 左到右 |
| 7 | != | 不等于 | 表达式!= 表达式 | 左到右 |
| 8 | & | 按位与 | 表达式&表达式 | 左到右 |
| 9 | ^ | 按位异或 | 表达式^表达式 | 左到右 |
| 10 | | | 按位或 | 表达式|表达式 | 左到右 |
| 11 | && | 逻辑与 | 表达式&&表达式 | 左到右 |
| 12 | || | 逻辑或 | 表达式||表达式 | 左到右 |
| 13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 |
| 14 | = | 赋值运算符 | 变量=表达式 | 右到左 |
| 14 | /= | 除后赋值 | 变量/=表达式 | 右到左 |
| 14 | *= | 乘后赋值 | 变量*=表达式 | 右到左 |
| 14 | %= | 取模后赋值 | 变量%=表达式 | 右到左 |
| 14 | += | 加后赋值 | 变量+=表达式 | 右到左 |
| 14 | -= | 减后赋值 | 变量-=表达式 | 右到左 |
| 14 | <<= | 左移后赋值 | 变量 | 右到左 |
| 14 | >>= | 右移后赋值 | 变量>>=表达式 | 右到左 |
| 14 | &= | 按位与后赋值 | 变量&=表达式 | 右到左 |
| 14 | ^= | 按位异或后赋值 | 变量^=表达式 | 右到左 |
| 14 | |= | 按位或后赋值 | 变量|=表达式 | 右到左 |
| 15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 |
注意:
- 如果不确定运算符的优先级,可以加括号
- 多用括号,让代码的可读性更好
1 |
|
多条件的if语句
语法:
1 | if (表达式一){ |
注意:
- 多条件的if语句本质上是嵌套的if语句
- 最多只能有127个条件分支
- 最后一个else可以没有
switch语句
switch也是一种选择结构的语句,可以代替简单的多条件的if语句。
语法:
1 | switch (表达式) |
注意:
case后面必须是整数和字符,或者是结果为整数和字符的表达式,但不能使用变量default不是必须的,当没有default时,如果全部的case匹配失败,那么就什么都不执行- 每个分支不要漏写
break;语句
循环的跳转
break和continue两个关键字用于控制循环体中代码的执行流程。
break跳出(中止)当前循环语句。
continue回到当前循环语句的首部。
示例:
1 |
|
for循环语句
语法:
1 | for (语句一 ; 表达式 ; 语句二){ |
1)循环开始的时候,先执行语句一,在整个循环过程中语句一只会被执行一次
2)计算表达式的值,如果为真,就执行一次循环体中的语句块
3)执行完语句块后,执行一次语句二
4)重复第2)步和第3),直到表达式的值不为真才结束for循环
注意:
- 不要纠结for循环与while循环的区别,它们本质上没有区别
- for循环一般需要一个相当于计数器的变量,在语句一中对它进行初始化,在语句二中进行计数操作
- 在for循环的语句一中,可以声明计数器变量
- 在for循环中,语句一、表达式和语句二都可以为空,for (; ; )等同于while (true)
- continue和break两个关键字也可以用在for循环体中。
示例:
1 |
|
函数基础
在复杂的程序中,如果全部的代码都写在main函数中,main函数体将非常庞大臃肿。
把任务分工到其它的函数中,main函数只负责程序的核心流程,具体的任务由其它函数完成。
这种思想就是模块化编程。
声明和定义函数的语法:
1 | 返回值的数据类型 函数名(参数一的数据类型 参数一, 参数二的数据类型 参数二,……) |
函数的声明:让编译器知道函数的存在,包括返回值的数据类型、函数名和参数列表。
函数的定义:函数的实现过程。
注意:
- 函数的声明和定义可以书写在一起,也可以分开,如果书写在一起,一般放在main函数的上面,如果分开,一般在main函数的上面声明,在main函数的下面定义
- 如果函数的声明和定义分开书写,函数的声明后面一定要有分号,函数的定义后面一定不能写分号
- 在同一个程序中,函数只需要声明和定义一次,也可以多次声明,但只能定义一次
- 函数的声明必须和函数的定义一致(返回值的数据类型、函数名和参数列表),如果函数名和参数列表不同,表示它们不是同一个函数。l return语句返回值的数据类型必须与函数的声明一致
- 在函数体中,return语句可以多次使用
- 如果函数的重点是实现功能,不关心返回值,返回值的数据类型填void,return语句后面就空着
- 函数可以没有任何参数
- 函数名是标识符,必须满足标识符的命名规则
- 在函数的声明和函数的定义中,参数命名可以不同,但是没必要这么书写。
1 |
|
函数的调用
1 | 语法:函数名(参数一,参数二,……) |
注意:
- 声明函数的代码必须放在调用之前,定义函数的代码可以放在调用之后
- 调用函数的时候,参数列表必须与函数的声明一致(参数的个数、书写的顺序和数据类型)
- 不管在什么地方,都不能调用main函数,但是,在普通函数中,可以调用其它的普通函数
- 调用函数的代码可以独占一条语句,也可以用于表达式(赋值运算、算术运算、关系运算、函数的参数)
- 如果函数用于表达式中,返回值的数据类型要匹配(否则可能会被隐式转换或编译错误)
- 如果函数有返回值,可以不关心它,忽略它
1 |
|
变量的作用域
作用域是指程序中变量存在(或生效)的区域,超过该区域变量就不能被访问。
变量分全局变量和局部变量两种,==全局变量在整个程序中都可以访问,局部变量只能在函数或语句块的内部才能访问==。
C++中定义变量的场景主要有五种:
- 在全部函数外面定义的是全局变量
- ==在头文件中定义的是全局变量==
- 在函数和==语句块==内部定义的是局部变量
- 函数的参数是该函数的局部变量
- 函数内部用
static修饰的是静态局部变量
1.全局变量
在整个程序生命周期内都是有效的,在定义位置之后的任意函数中都能访问。
全局变量在主程序退出时由系统收回内存空间。
2.局部变量
在函数或语句块内部的语句使用,在函数或语句块外部是不可用的。
局部变量在函数返回或语句块结束时由系统收回内存空间。
3.静态局部变量
用static修饰的局部变量生命周期和程序相同,并且只会被初始化一次
其作用域为局部,当定义它的函数或语句块结束时,其作用域随之结束
当程序想要使用全局变量的时候应该先考虑使用static(考虑到数据安全性)
4.注意事项
- 全局变量和静态局部变量自动初始化为0
- 局部变量不会自动初始化,其值是不确定的,程序中应该有初始化局部变量的代码,否则编译可能会报错(不同的编译器不一样)
- 局部变量和全局变量的名称可以相同,在某函数或语句块内部,如果局部变量名与全局变量名相同,就会屏蔽全局变量而使用局部变量,如果想使用全局变量,可以在变量名前加两个冒号(::)
- for循环初始化语句中定义的变量的作用域是for语句块
函数参数的传递
调用函数的时候,调用者把数值赋给了函数的参数
实参:调用者程序中书写的在函数名括号中的参数,可以是常量、变量和表达式
形参:函数的参数列表
在函数定义的代码中,修改形参的值,会不会影响实参。
示例:
1 |
|
函数分文件编写
头文件(.h):需要包含的头文件,声明全局变量,函数的声明,数据结构和类的声明等
源文件(.cpp):函数的定义、类的定义
主程序:main函数,程序的核心流程,需要用#include "头文件名"把头文件包含进来
编译:
Windows是集成开发环境,不需要写编译指令。
在Linux系统下,把全部的源文件一起编译,如:g++ -o demo demo.cpp tools.cpp girls.cpp
示例:
1 | /*demo01.cpp*/ |
VS中调试程序
F9设置/取消断点
F5/F10开始调试
Shift+F5放弃调试
F10逐过程执行
F11逐语句执行(可进入函数内部)
局部变量窗口显示了变量的值,也可以修改
递归函数
一个函数可以调用另一个函数,作为特例,如果函数调用了自己,就像故事中提到了同样的故事一样,我们把函数在运行时调用自己的情况叫做递归。
递归函数中一定要有递归终止的条件,否则是死递归。
示例:
1 |
|
sizeof运算符
sizeof运算符用于求数据类型或变量占用的内存空间
用于数据类型:sizeof(数据类型)
用于变量:sizeof(变量名) 或
sizeof 变量名
注意:
- 在32位和64位操作系统中,同一种数据类型占用的内存空间可能不一样
- 字符串(
string)不是C++的基本数据类型,用sizeof求它占用内存的大小没有意义

1 |
|
字符型的基本概念
字符型(char)占用的内存空间是1个字节,书写用单引号包含。
在内存中,不存放字符本身,而是存放与它对应的编码,即ASCII码。
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是现今最通用的单字节编码方案,包含了33个控制字符(具有特殊含义无法显示的字符)和95个可显示字符。
'X' -> 88 01011000 'a'->97 01100001 '3'->51 00110011
1.ASCII 控制字符 (0~31)
| 十进制 | 符号 | 中文解释 | 十进制 | 符号 | 中文解释 |
|---|---|---|---|---|---|
| 0 | NULL | 空字符 | 16 | DLE | 数据链路转义 |
| 1 | SOH | 标题开始 | 17 | DC1 | 设备控制 1 |
| 2 | STX | 正文开始 | 18 | DC2 | 设备控制 2 |
| 3 | ETX | 正文结束 | 19 | DC3 | 设备控制 3 |
| 4 | EOT | 传输结束 | 20 | DC4 | 设备控制 4 |
| 5 | ENQ | 询问 | 21 | NAK | 拒绝接收 |
| 6 | ACK | 收到通知 | 22 | SYN | 同步空闲 |
| 7 | BEL | 铃 | 23 | ETB | 传输块结束 |
| 8 | BS | 退格 | 24 | CAN | 取消 |
| 9 | HT | 水平制表符 | 25 | EM | 介质中断 |
| 10 | LF | 换行键 | 26 | SUB | 替换 |
| 11 | VT | 垂直制表符 | 27 | ESC | 换码符 |
| 12 | FF | 换页键 | 28 | FS | 文件分隔符 |
| 13 | CR | 回车键 | 29 | GS | 组分隔符 |
| 14 | SO | 移出 | 30 | RS | 记录分离符 |
| 15 | SI | 移入 | 31 | US | 单元分隔符 |
2.ASCII 可显示字符 (32~127)
| 十进制 | 符号 | 中文解释 | 十进制 | 符号 | 中文解释 |
|---|---|---|---|---|---|
| 32 | 空格 | 80 | P | 大写字母 P | |
| 33 | ! | 感叹号 | 81 | Q | 大写字母 Q |
| 34 | " | 双引号 | 82 | R | 大写字母 R |
| 35 | # | 井号 | 83 | S | 大写字母 S |
| 36 | $ | 美元符 | 84 | T | 大写字母 T |
| 37 | % | 百分号 | 85 | U | 大写字母 U |
| 38 | & | 与 | 86 | V | 大写字母 V |
| 39 | ' | 单引号 | 87 | W | 大写字母 W |
| 40 | ( | 左括号 | 88 | X | 大写字母 X |
| 41 | ) | 右括号 | 89 | Y | 大写字母 Y |
| 42 | * | 星号 | 90 | Z | 大写字母 Z |
| 43 | + | 加号 | 91 | [ | 左中括号 |
| 44 | , | 逗号 | 92 | \ | 斜线 |
| 45 | - | 减号 | 93 | ] | 右中括号 |
| 46 | . | 句点或小数点 | 94 | ^ | 音调符号 |
| 47 | / | 反斜线 | 95 | _ | 下划线 |
| 48 | 0 | 数字0的符号 | 96 | ` | 重音符 |
| 49 | 1 | 数字1的符号 | 97 | a | 小写字母 a |
| 50 | 2 | 数字2的符号 | 98 | b | 小写字母 b |
| 51 | 3 | 数字3的符号 | 99 | c | 小写字母 c |
| 52 | 4 | 数字4的符号 | 100 | d | 小写字母 d |
| 53 | 5 | 数字5的符号 | 101 | e | 小写字母 e |
| 54 | 6 | 数字6的符号 | 102 | f | 小写字母 f |
| 55 | 7 | 数字7的符号 | 103 | g | 小写字母 g |
| 56 | 8 | 数字8的符号 | 104 | h | 小写字母 h |
| 57 | 9 | 数字9的符号 | 105 | i | 小写字母 i |
| 58 | : | 冒号 | 106 | j | 小写字母 j |
| 59 | ; | 分号 | 107 | k | 小写字母 k |
| 60 | < | 小于 | 108 | l | 小写字母 l |
| 61 | = | 等号 | 109 | m | 小写字母 m |
| 62 | > | 大于 | 110 | n | 小写字母 n |
| 63 | ? | 问号 | 111 | o | 小写字母 o |
| 64 | @ | 电子邮件符号 | 112 | p | 小写字母 p |
| 65 | A | 大写字母 A | 113 | q | 小写字母 q |
| 66 | B | 大写字母 B | 114 | r | 小写字母 r |
| 67 | C | 大写字母 C | 115 | s | 小写字母 s |
| 68 | D | 大写字母 D | 116 | t | 小写字母 t |
| 69 | E | 大写字母 E | 117 | u | 小写字母 u |
| 70 | F | 大写字母 F | 118 | v | 小写字母 v |
| 71 | G | 大写字母 G | 119 | w | 小写字母 w |
| 72 | H | 大写字母 H | 120 | x | 小写字母 x |
| 73 | I | 大写字母 I | 121 | y | 小写字母 y |
| 74 | J | 大写字母 J | 122 | z | 小写字母 z |
| 75 | K | 大写字母 K | 123 | { | 左大括号 |
| 76 | L | 大写字母 L | 124 | | | 竖线 |
| 77 | M | 大写字母 M | 125 | } | 右大括号 |
| 78 | N | 大写字母 N | 126 | ~ | 波浪号 |
| 79 | O | 大写字母 O | 127 | 删除 |
- 32是空格
- 48~57是0到9十个阿拉伯数字
- 65~90是26个大写英文字母
- 97~122号是26个小写英文字母
- 其余的是一些标点符号、运算符号等
- 第127个字符表示的是键盘上的删除键
3.字符的本质
- 字符的本质是整数,取值范围是0~127
- 在书写的时候可以用单引号包含,也可以用整数
- 如果书写的时候用单引号包含,程序执行的时候,将把符号解释为对应的整数
- 显示的时候,把整数解释为对应的符号,也可以直接显示整数
- 可以与整数进行任何运算,运算的时候,书写方式可以用字符,也可以用整数
- C++为什么没有提供1字节的整型?
- 字符型也可以用unsigned修饰,意义何在?
示例:
1 |
|
转义字符
在C++程序中,使用转义字符的原因有两个:
- 控制字符没有符号,无法书写,只能用其它的符号代替
- 某些符号已被C++征用,语义冲突,只能用其它的符号代替
| ASCII码值 | 转义字符 | 含义 |
|---|---|---|
| 0 | \0 |
空,给字符型变量赋值时可以直接书写0 |
| 10 | \n |
换行(LF) ,将当前位置移到下一行开头 |
| 13 | \r |
回车(CR) ,将当前位置移到本行开头 |
| 9 | \t |
水平制表(HT) (跳到下一个TAB位置) |
| 92 | \\ |
斜线 |
| 34 | \" |
双引号,书写字符时不必转义 |
| 39 | \' |
单引号,书写字符串中不必转义 |
| 7 | \a |
警报 |
| 8 | \b |
退格(BS) ,将当前位置移到前一列 |
| 12 | \f |
换页(FF),将当前位置移到下页开头 |
| 11 | \v |
垂直制表(VT) |
示例:
1 |
|
C++11的原始字面量
原始字面量(值)可以直接表示字符串的实际含义,不需要转义和连接。
语法:
1 | R"(字符串的内容)" |
示例:
1 |
|
字符串型
C++风格字符串:string 变量名="字符串的内容";
C风格字符串:char 变量名[]="字符串的内容";
C风格字符串的本质是字符数组,C++风格字符串的本质是类,它封装了C风格字符串。
C++风格字符串的常用操作:
赋值:变量名="字符串的内容"
拼接:变量名=变量名+"字符串的内容一"+"字符串的内容一"+......+"字符串的内容n"
如果字符串的内容都是常量,不要写加号(+),如果内容很长,可以分成多行书写。
比较:支持
==、!=、>和<关系运算符,常用的是==和!=
示例:
1 |
|
布尔型
在C和C++中,关系运算和逻辑运算的结果有两种:真和假。
C语言用0表示假,非0表示真。
为了提高代码的可读性,C++新增了 bool
类型,占用1字节的内存,用true表示真,false表示假。
bool类型本质上是1字节的整数(unsigned char),取值只有1和0。
在程序中,书写的时候可以用true和false,编译器把它们解释为1和0。
如果对bool型变量赋非0的值,将转换成1。
用cin输入和cout输出的时候,仍是1和0,不会被解释为true和false。
示例:
1 |
|
数据类型的转换
计算机进行运算时,要求各操作数的类型具有相同的大小和存储方式
在实际开发中,不同类型的数据进行混合运算是基本需求
自动类型转换:某些类型的转换编译器可以隐式的进行,不需程序员干预
强制类型转换:有些类型的转换需要程序员显式指定
1.自动类型转换
不同数据类型的差别在于取值范围和精度,数据的取值范围越大,精度越高。
整型从低到高:
char -> short -> int -> long -> long long
浮点型从低到高:
float -> double -> long double
自动类型转换的规则如下:
- 如果一个表达式中出现了不同类型操作数的混合运算,较低类型将自动向较高类型转换
- 当表达式中含有浮点型操作数时,所有操作数都将转换为浮点型
- 赋值运算的右值类型与左值类型不一致时,将右值类型提升/降低为左值类型
- 赋值运算右值超出了左值类型的表示范围,把该右值截断后赋给左值,所得结果可能毫无意义。
2.强制类型转换
为了让程序设计更灵活,转换的目的更清晰,C++提供了强制类型转换的方法,也称之为显式转换。
强制类型转换的语法:(目标类型)表达式或目标类型(表达式)
注意:
- 如果使用强制转换,表示程序员已有明确的目的
- 如果转换的行为不符合理,后果由程序员承担
- 如果采用了强制类型转换,编译的告警信息将不再出现
- 类型转换运算符的优先级比较高,如果没把握就加括号
示例:
1 |
|
数据类型的别名typedef
创建数据类型的别名有两个目的:
- 为名称复杂的类型创建别名,方便书写和记忆
- 创建与平台无关的数据类型,提高程序的兼容性。
语法:typedef 原数据类型名 别名;
C++11还可以用using关键字创建数据类型的别名。
语法:using 别名=原数据类型名;
示例:
1 |
|
指针的基本概念
1.变量的地址
变量是内存变量的简称,在C++中,每定义一个变量,系统就会给变量分配一块内存,内存是有地址的

C++用运算符&获取变量在内存中的起始地址
语法:&变量名
2.指针变量
指针变量简称指针,它是一种特殊的变量,专用于存放变量在内存中的起始地址。
语法:数据类型 *变量名;
数据类型必须是合法的C++数据类型(int、char、double或其它自定义的数据类型)。
星号*与乘法中使用的星号是相同的,但是,在这个场景中,星号用于表示这个变量是指针。
3.对指针赋值
不管是整型(int)、浮点型(float/double)、字符型(char),还是其它的数据类型的变量,它的地址都是一个十六进制数。我们用整型指针存放整数型变量的地址;用字符型指针存放字符型变量的地址;用浮点型指针存放浮点型变量的地址,用自定义数据类型指针存放自定义数据类型变量的地址。
语法:指针=&变量名;
注意:
- 对指针的赋值操作也通俗的被称为“指向某变量”,被指向的变量的数据类型称为“基类型”
- 如果指针的数据类型与基类型不符,编译会出现警告。但是,可以强制转换它们的类型
4.指针占用的内存
指针也是变量,是变量就要占用内存空间。
在64位的操作系统中,不管是什么类型的指针,占用的内存都是8字节。
在C++中,指针是复合数据类型,复合数据类型是指基于其它类型而定义的数据类型,在程序中,int是整型类型,int*是整型指针类型,int*可以用于声明变量,可以用于sizeof运算符,可以用于数据类型的强制转换,总的来说,把int*当成一种数据类型就是了。
使用指针
声明指针变量后,在没有赋值之前,里面是乱七八糟的值,这时候不能使用指针。
指针存放变量的地址,因此,指针名表示的是地址(就像变量名可以表示变量的值一样)
*运算符被称为间接值或解除引用(解引用)运算符,将它用于指针,可以得到该地址的内存中存储的值
*也是乘法符号,C++根据上下文来确定所指的是乘法还是解引用。
变量和指向变量的指针就像同一枚硬币的两面

哪个银行? 什么东西? 数额
程序在存储数据的时候,必须跟踪三种基本属性:
- 数据存储在哪里
- 数据是什么类型
- 数据的值是多少
用两种策略可以达到以上目的:
- 声明一个普通变量,声明时指出数据类型和变量名(符号名),系统在内部跟踪该内存单元
- 声明一个指针变量,存储的值是地址,而不是值本身,程序直接访问该内存单元
指针用于函数的参数
如果把函数的形参声明为指针,调用的时候把实参的地址传进去,形参中存放的是实参的地址,在函数中通过解引用的方法直接操作内存中的数据,可以修改实数的值,这种方法被通俗的称为地址传递或传地址。
值传递:函数的形参是普通变量。
传地址的意义如下:
- 可以在函数中修改实参的值
- 减少内存拷贝,提升性能
示例:
1 |
|
用const修饰指针
1.常量指针
语法:const 数据类型 *变量名;
不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
注意:
- 指向的变量(对象)可以改变(之前是指向变量a的,后来可以改为指向变量b)
- 一般用于修饰函数的形参,表示不希望在函数里修改内存地址中的值
- 如果用于形参,虽然指向的对象可以改变,但这么做没有任何意义
- 如果形参的值不需要改变,建议加上const修饰,程序可读性更好
2.指针常量
语法:数据类型 * const 变量名;
指向的变量(对象)不可改变。
注意:
- 在定义的同时必须初始化,否则没有意义
- 可以通过解引用的方法修改内存地址中的值
- C++编译器把指针常量做了一些特别的处理,改头换面之后,有一个新的名字,叫引用
3.常指针常量
语法:const 数据类型 * const 变量名;
指向的变量(对象)不可改变,不能通过解引用的方法修改内存地址中的值。
常引用。
常量指针:指针指向可以改,指针指向的值不可以更改。
指针常量:指针指向不可以改,指针指向的值可以更改。
常指针常量:指针指向不可以改,指针指向的值不可以更改。
记忆秘诀: ==*表示指针,指针在前先读指针;指针在前指针就不允许改变==
常量指针:const 数据类型 *变量名
指针常量:数据类型 * const 变量名
void关键字
在C++中,void表示为无类型,主要有三个用途:
1.函数的返回值用void,表示函数没有返回值
1 | void func(int a,int b){ |
2.函数的参数填void,表示函数不需要参数(或者让参数列表空着)
1 | int func(void){ |
3.函数的形参用void *,表示接受任意数据类型的指针
注意:
- 不能用void声明变量,它不能代表一个真实的变量,但是,用
void *可以 - 不能对
void *指针直接解引用(需要转换成其它类型的指针)* *把其它类型的指针赋值给void*指针不需要转换- 把
void *指针赋值给把其它类型的指针需要转换
示例:
1 |
|
C++内存模型
在 C++ 中,程序运行时,内存主要分成四个区,分别是栈、堆、数据段和代码段

栈:存储局部变量、函数参数和返回值。
堆:存储动态开辟内存的变量。
数据段:存储全局变量和静态变量。
代码段:存储可执行程序的代码和常量(例如字符常量),此存储区不可修改。
栈和堆的主要区别:
- 管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需手动释放,若程序中不释放,程序结束时由操作系统回收
- 空间大小不同:堆内存的大小受限于物理内存空间;而栈就小得可怜,一般只有8M(可以修改系统参数)
- 分配方式不同:堆是动态分配;栈有静态分配和动态分配(都是自动释放)
- 分配效率不同:栈是系统提供的数据结构,计算机在底层提供了对栈的支持,进栈和出栈有专门的指令,效率比较高;堆是由C++函数库提供的
- 是否产生碎片:对于栈来说,进栈和出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的分配和释放,会造成内存空间的不连续,容易产生碎片,太多的碎片会导致性能的下降
- 增长方向不同:栈向下增长,以降序分配内存地址;堆向上增长,以升序分配内存地址。
动态分配内存new和delete
使用堆区的内存有四个步骤:
1)声明一个指针;
2)用new运算符向系统申请一块内存,让指针指向这块内存;
3)通过对指针解引用的方法,像使用变量一样使用这块内存;
4)如果这块内存不用了,用delete运算符释放它
申请内存的语法:
1 | new 数据类型(初始值); // C++11支持{} |
如果申请成功,返回一个地址;如果申请失败,返回一个空地址(暂时不考虑失败的情况)
释放内存的语法:delete 地址;
释放内存不会失败(还钱不会失败)。
注意:
- 动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据
- 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存
- 动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收
- 就算指针的作用域已失效,所指向的内存也不会释放
- 用指针跟踪已分配的内存时,不能跟丢
示例:
1 |
|
二级指针
指针是指针变量的简称,也是变量,是变量就有地址。
指针用于存放普通变量的地址。
二级指针用于存放指针变量的地址。
声明二级指针的语法:数据类型** 指针名;
使用指针有两个目的:
- 传递地址;
- 存放动态分配的内存的地址
在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址,形参用二级指针。
把普通变量的地址传入函数后可以在函数中修改变量的值;把指针的地址传入函数后可以在函数中指针的值。
示例:
1 |
|
空指针
在C和C++中,用0或NULL都可以表示空指针。
声明指针后,在赋值之前,让它指向空,表示没有指向任何地址。
1.使用空指针的后果
如果对空指针解引用,程序会崩溃。
如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把指针指向空。
在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。
为什么空指针访问会出现异常?
NULL指针分配的分区:其范围是从 0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。
2)C++11的nullptr
用0和NULL表示空指针会产生歧义,C++11建议用nullptr表示空指针,也就是(void *)0。
NULL在C++中就是0,这是因为在C++中void* 类型是不允许隐式转换成其他类型的,所以之前C++中用0来代表空指针,但是在重载整形的情况下,会出现上述的问题。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,因此,建议用nullptr替代NULL吧,而NULL就当做0使用。
注意:在Linux平台下,如果使用nullptr,编译需要加-std=c++11参数
示例:
1 |
|
函数指针
函数的二进制代码存放在内存四区中的代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其它函数。
使用函数指针的三个步骤:
- 声明函数指针;
- 让函数指针指向函数的地址;
- 通过函数指针调用函数
1)声明函数指针
声明普通指针时,必须提供指针的类型。同样,声明函数指针时,也必须提供函数类型,函数的类型是指返回值和参数列表(函数名和形参名不是)
假设函数的原型是:
1 | int func1(int bh,string str); |
则函数指针的声明是:
1 | int (*pfa)(int,string); |
pfa、pfb、pfc是函数指针名,必须用括号,否则就成了返回指针的函数
示例:
1 |
|
2)函数指针的赋值
函数名就是函数的地址
函数指针的赋值:函数指针名=函数名;
3)函数指针的调用
1 | (*函数指针名)(实参); |
示例:
1 |
|
数组
一维数组的基本概念
数组是一组数据类型相同的变量,可以存放一组数据。
1.创建数组
声明数组的语法:数据类型 数组名[数组长度];
注意:数组长度必须是整数,可以是常量,也可以是变量和表达式
2.数组的使用
可以通过下标访问数组中元素,数组下标从0开始。
数组中每个元素的特征和使用方法与单个变量完全相同。
语法:数组名[数组下标]
注意:
- 数组下标也必须是整数,可以是常量,也可以是变量
- 合法的数组下标取值是:0~(数组长度-1)
3.数组占用内存的情况
数组在内存中占用的空间是连续的。
用sizeof(数组名)可以得到整个数组占用内存空间的大小(只适用于C++基本数据类型)
4.数组的初始化
声明的时候初始化:
1 | 数据类型 数组名[数组长度] = { 值1,值2,值3, ...... , 值n}; |
注意:如果{}内不足数组长度个数据,剩余数据用0补全,但是,不建议这么用,你可能在数组中漏了某个值。如果想把数组中全部的元素初始化为0,可以在{}内只填一个0或什么也不填。
C++11标准可以不写等于号。
5.清空数组
用memset()函数可以把数组中全部的元素清零。(只适用于C++基本数据类型)
函数原型:void *memset(void *s, int c, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
6.复制数组
用memcpy()函数可以把数组中全部的元素复制到另一个相同大小的数组。(只适用于C++基本数据类型)
函数原型:void *memcpy(void *dest, const void *src, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
示例:
1 |
|
一维数组和指针
1.指针的算术
将一个整型变量加1后,其值将增加1。
但是,将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数
2.数组的地址
- 数组在内存中占用的空间是连续的
- C++将数组名解释为数组第0个元素的地址
- 数组第0个元素的地址和数组首地址的取值是相同的
- 数组第n个元素的地址是:
数组首地址+n - C++编译器把
数组名[下标]解释为*(数组首地址+下标)
3.数组的本质
数组是占用连续空间的一块内存,数组名被解释为数组第0个元素的地址。C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。
4.数组名不一定会被解释为地址
在多数情况下,C++将数组名解释为数组的第0个元素的地址,但是,将sizeof运算符用于数据名时,将返回整个数组占用内存空间的字节数。
可以修改指针的值,但数组名是常量,不可修改。
示例:
1 |
|
1 |
|
1 |
|
一维数组用于函数的参数
1.指针的数组表示
在C++内部,用指针来处理数组
C++编译器把 数组名[下标] 解释为
*(数组首地址+下标)
C++编译器把 地址[下标] 解释为
*(地址+下标)
2.一维数组用于函数的参数
一维数组用于函数的参数时,只能传数组的地址,并且必须把数组长度也传进去,除非数组中有最后一个元素的标志。
书写方法有两种:
1 | void func(int* arr, int len); |
注意:
在函数中,可以用数组表示法,也可以用指针表示法。
在函数中,不要对指针名用sizeof运算符,它不是数组名。
示例:
1 |
|
1 |
|
用new动态创建一维数组
普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。
动态创建一维数组的语法:数据类型 *指针=new 数据类型[数组长度];
释放一维数组的语法:delete [] 指针;
注意:
- 动态创建的数组没有数组名,不能用sizeof运算符
- 可以用数组表示法和指针表示法两种方式使用动态创建的数组
- 必须使用delete[]来释放动态数组的内存(不能只用delete)
- 不要用delete[]来释放不是new[]分配的内存
- 不要用delete[]释放同一个内存块两次(否则等同于操作野指针)
- 对空指针用delete[]是安全的(释放内存后,应该把指针置空nullptr)
- 声明普通数组的时候,数组长度可以用变量,相当于在栈上动态创建数组,并且不需要释放
- 如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常
- 为什么用delete[]释放数组的时候,不需要指定数组的大小?因为系统会自动跟踪已分配数组的内存
示例:
1 |
|
各种形参的使用场景
传值、传地址和传引用的指导原则《C++ Primer Plus》
1.如果不需要在函数中修改实参
- 如果实参很小,如C++内置的数据类型或小型结构体,则按值传递
- 如果实参是数组,则使用const指针,因为这是唯一的选择(没有为数组建立引用的说法)
- 如果实参是较大的结构,则使用const指针或const引用
- 如果实参是类,则使用const引用,传递类的标准方式是按引用传递(类设计的语义经常要求使用引用)。
2.如果需要在函数中修改实参
- 如果实参是内置数据类型,则使用指针。只要看到func(&x)的调用,表示函数将修改x
- 如果实参是数组,则只能使用指针
- 如果实参是结构体,则使用指针或引用
- 如果实参是类,则使用引用。
当然,这只是一些指导原则,很可能有充分的理由做出其他的选择。
例如:对于基本类型,cin使用引用,因此可以使用cin>>a,而不是cin>>&a
引用
引用的基本概念
引用变量是C++新增的复合类型
引用是已定义的变量的别名
引用的主要用途是用作函数的形参和返回值
声明/创建引用的语法:数据类型 &引用名=原变量名;
注意:
- 引用的数据类型要与原变量名的数据类型相同
- 引用名和原变量名可以互换,它们值和内存单元是相同的
- 必须在声明引用的时候初始化,初始化后不可改变
- C和C++用&符号来指示/取变量的地址,C++给&符号赋予了另一种含义
示例:
1 |
|
引用的本质
引用是指针常量的伪装
引用是编译器提供的一个有用且安全的工具,去除了指针的一些缺点,禁止了部分不安全的操作
变量是什么?变量就是一个在程序执行过程中可以改变的量
换一个角度,变量是一块内存区域的名字,它代表了这块内存区域,当我们对变量进行修改的时候,会引起内存区域中内容的改变。
在计算机看来,内存区域根本就不存在什么名字,它仅有的标志就是它的地址,因此我们若想修改一块内存区域的内容,只有知道他的地址才能实现。
所谓的变量只不过是编译器给我们进行的一种抽象,让我们不必去了解更多的细节,降低我们的思维跨度而已
程序员拥有引用,但编译器仅拥有指针(地址)
引用的底层机制实际上是和指针一样的。不要相信有别名,不要认为引用可以节省一个指针的空间,因为这一切不会发生,编译器还是会把引用解释为指针
引用和指针本质上没有区别
示例:
1 |
|
引用用于函数的参数
把函数的形参声明为引用,调用函数的时候,形参将成为实参的别名
这种方法也叫按引用传递或传引用。(传值、传地址、传引用只是说法不同,其实都是传值。)
引用的本质是指针,传递的是变量的地址,在函数中,修改形参会影响实参。
1)传引用的代码更简洁。
2)传引用不必使用二级指针。
3)引用的属性和特别之处。
示例:
1 |
|
1 |
|
1 |
|
引用的形参和const
如果引用的数据对象类型不匹配,当引用为const时,C++将创建临时变量,让引用指向临时变量
什么时候将创建临时变量呢?
- 引用是
const - 数据对象的类型是正确的,但不是左值
- 数据对象的类型不正确,但可以转换为正确的类型。
结论:如果函数的实参不是左值或与const引用形参的类型不匹配,那么C++将创建正确类型的匿名变量,将实参的值传递给匿名变量,并让形参来引用该变量。
将引用形参声明为const的理由有三个:
- 使用const可以避免无意中修改数据的编程错误
- 使用const使函数能够处理const和非const实参,否则将只能接受非const实参
- 使用const,函数能正确生成并使用临时变量
左值是可以被引用的数据对象,可以通过地址访问它们,例如:变量、数组元素、结构体成员、引用和解引用的指针
非左值包括字面常量(用双引号包含的字符串除外)和包含多项的表达式
示例:
1 |
|
引用用于函数的返回值
传统的函数返回机制与值传递类似。
函数的返回值被拷贝到一个临时位置(寄存器或栈),然后调用者程序再使用这个值。
double m=sqrt(36); // sqrt()是求平方根函数。
sqrt(36)的返回值6被拷贝到临时的位置,然后赋值给m。
cout << sqrt(25);
sqrt(25)的返回值5被拷贝到临时的位置,然后传递给cout。
如果返回的是一个结构体,将把整个结构体拷贝到临时的位置。
如果返回引用不会拷贝内存。
语法:
返回值的数据类型& 函数名(形参列表);
注意:
- 如果返回局部变量的引用,其本质是野指针,后果不可预知
- 可以返回函数的引用形参、类的成员、全局变量、静态变量
- 返回引用的函数是被引用的变量的别名,将const用于引用的返回类型
示例:
1 |
|
各种形参的使用场景
传值、传地址和传引用的指导原则《C++ Primer Plus》
1.如果不需要在函数中修改实参
- 如果实参很小,如C++内置的数据类型或小型结构体,则按值传递
- 如果实参是数组,则使用const指针,因为这是唯一的选择(没有为数组建立引用的说法)
- 如果实参是较大的结构,则使用const指针或const引用
- 如果实参是类,则使用const引用,传递类的标准方式是按引用传递(类设计的语义经常要求使用引用)
2.如果需要在函数中修改实参
- 如果实参是内置数据类型,则使用指针。只要看到
func(&x)的调用,表示函数将修改x - 如果实参是数组,则只能使用指针
- 如果实参是结构体,则使用指针或引用
- 如果实参是类,则使用引用
当然,这只是一些指导原则,很可能有充分的理由做出其他的选择。
例如:对于基本类型,cin使用引用,因此可以使用cin>>a,而不是cin>>&a
函数的默认参数
默认参数是指调用函数的时候,如果不书写实参,那么将使用的一个缺省值
语法:
1 | 返回值 函数名(数据类型 参数=值, 数据类型 参数=值,……); |
注意:
- 如果函数的声明和定义是分开书写的,在函数声明中书写默认参数,函数的定义中不能书写默认参数
- 函数必须从右到左设置默认参数。也就是说,如果要为某个参数设置默认值,则必须为它后面所有的参数设置默认值
- 调用函数的时候,如果指定了某个参数的值,那么该参数前面所有的参数都必须指定。
示例:
1 |
|
函数重载
函数重载(函数多态)是指设计一系列同名函数,让它们完成相同(似)的工作。
C++允许定义名称相同的函数,条件是它们的特征(形参的个数、数据类型和排列顺序)不同。
1 | int func(short a ,string b); |
调用重载函数的时候,在代码中我们用相同的函数名,但是,后面的实参不一样,编译器根据实参与重载函数的形参进行匹配,然后决定调用具体的函数,如果匹配失败,编译器将视为错误。
在实际开发中,视需求重载各种数据类型,不要重载功能不同的函数。
注意:
- 使用重载函数时,如果数据类型不匹配,C++尝试使用类型转换与形参进行匹配,如果转换后有多个函数能匹配上,编译将报错
- 引用可以作为函数重载的条件,但是,调用重载函数的时候,如果实参是变量,编译器将形参类型的本身和类型引用视为同一特征
- 如果重载函数有默认参数,调用函数时,可能导致匹配失败
- const不能作为函数重载的特征
- 返回值的数据类型不同不能作为函数重载的特征
- C++的名称修饰:编译时,对每个函数名进行加密,替换成不同名的函数。
1 | void MyFunctionFoo(int,float); |
示例:
1 |
|
内联函数
C++将内联函数的代码组合到程序中,可以提高程序运行的速度。
语法:在函数声明和定义前加上关键字inline
通常的做法是将函数声明和定义写在一起
注意:
- 内联函数节省时间,但消耗内存
- 如果函数过大,编译器可能不将其作为内联函数
- 内联函数不能递归
示例:
1 |
|
面向对象编程(OOP)
从结构体到类
对面向对象编程来说,一切都是对象,对象用类来描述
类把对象的数据和操作数据的方法作为一个整体考虑
定义类的语法:
1 | class 类名 |
注意:
- 类的成员可以是变量,也可以是函数
- 类的成员变量也叫属性
- 类的成员函数也叫方法/行为,类的成员函数可以定义在类的外面
- 用类定义一个类的变量叫创建(或实例化)一个对象
- 对象的成员变量和成员函数的作用域和生命周期与对象的作用域和生命周期相同
1 |
|
1 |
|
1 |
|
类的访问权限
类的成员有三种访问权限:public、private和protected,分别表示公有的、私有的和受保护的
在类的内部(类的成员函数中),无论成员被声明为public还是private,都可以访问
在类的外部(定义类的代码之外),只能访问public成员,不能访问
private、protected成员
在一个类体的定义中,private和
public可以出现多次
结构体的成员缺省为public,类的成员缺省为private
private的意义在于隐藏类的数据和实现,把需要向外暴露的成员声明为public
简单使用类
编程思想和方法的改变,披着C++外衣的C程序员。
1)类的成员函数可以直接访问该类其它的成员函数(可以递归)。
2)类的成员函数可以重载,可以使用默认参数。
3)类指针的用法与结构体指针用法相同。
4)类的成员可以是任意数据类型(类中枚举)。
5)可以为类的成员指定缺省值(C++11标准)。
6)类可以创建对象数组,就像结构体数组一样。
7)对象可以作为实参传递给函数,一般传引用。
8)可以用new动态创建对象,用delete释放对象。
9)在类的外部,一般不直接访问(读和写)对象的成员,而是用成员函数。数据隐藏是面向对象编程的思想之一。
10)对象一般不用memset()清空成员变量,可以写一个专用于清空成员变量的成员函数。
11)对类和对象用sizeof运算意义不大,一般不用。
12)用结构体描述纯粹的数据,用类描述对象。
13)在类的声明中定义的函数都将自动成为内联函数;在类的声明之外定义的函数如果使用了inline限定符,也是内联函数。
14)为了区分类的成员变量和成员函数的形参,把成员变量名加m_前缀或_后缀,如m_name或name_。
15)类的分文件编写。
构造函数和析构函数
构造函数:在创建对象时,自动的进行初始化工作。
析构函数:在销毁对象前,自动的完成清理工作。
1.构造函数
语法:类名(){......}
- 访问权限必须是public
- 函数名必须与类名相同
- 没有返回值,不写void
- 可以有参数,可以重载,可以有默认参数
- 创建对象时只会自动调用一次,不能手工调用。
2.析构函数
语法:~类名(){......}
- 访问权限必须是
public - 函数名必须在类名前加
~ - 没有返回值,也不写
void - 没有参数,不能重载
- 销毁对象前只会自动调用一次,但是可以手工调用
注意:
1) 如果没有提供构造/析构函数,编译器将提供空实现的构造/析构函数。
2) 如果提供了构造/析构函数,编译器将不提供空实现的构造/析构函数。
3) 创建对象的时候,如果重载了构造函数,编译器根据实参匹配相应的构造函数。没有参数的构造函数也叫默认构造函数。
4) 创建对象的时候不要在对象名后面加空的圆括号,编译器误认为是声明函数。(如果没有构造函数、构造函数没有参数、构造函数的参数都有默认参数)
5) 在构造函数名后面加括号和参数不是调用构造函数,是创建匿名对象。
6) 接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值(可能会导致问题,不推荐)。
CGirl girl =10;
7) 以下两行代码有本质的区别:
CGirl girl = CGirl("西施"20); // 显式创建对象。
CGirl girl; // 创建对象。
girl = CGirl("西施"20); // 创建匿名对象,然后给现有的对象赋值。
8) 用new/delete创建/销毁对象时,也会调用构造/析构函数。
9) 不建议在构造/析构函数中写太多的代码,可以调用成员函数。
10) 除了初始化,不建议让构造函数做太多工作(只能成功不会失败)。
11) C++11支持使用统一初始化列表。
CGirl girl = {"西施"20};
CGirl girl {"西施"20};
CGirl* girl = new CGirl{ "西施"20 };
12) 如果类的成员也是类,创建对象的时候,先构造成员类;销毁对象的时候,先析构自身,再析构成员类(视频中有误)。
示例: