C++基础
输出语句
#include <iostream>
int main()
{
std::cout << "Hello World!\n"; // 输出HelloWorld
std::cout<<std::endl; // 输出换行
}
如果要使用cout输出变量的地址,最好是使用以下语句,否则cout可能将地址当做字符串处理
std::cout << (void*)&variable << std::endl;
命名空间
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!\n"; // 输出HelloWorld
cout<<endl; // 输出换行
}
变量
变量类型
int、float、double、char、string、bool(unsigned char)
signed:有符号的,可以表示正数和负数
unsigned:无符号的,只能表示正数
如果给bool变量赋值数字,非0将会被转换为1。
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!\n"; // 输出HelloWorld
cout << endl; // 输出换行
string name; // 姓名
int age;
double weight;
char sex;
bool flag;
name = "C Primer Plus";
age = 18;
weight = 500;
sex = 'M';
flag = true; // bool值在CPP里就是true:1 flase:0
cout << "name:" << name << " age:" << age << " weight:" << weight << " sex:" << sex << " flag:" << flag;
return 0;
}
变量作用域
全局变量和静态局部变量都会被自动初始化为0
输入语句
#include <iostream>
using namespace std;
int main()
{
cin >> name;
cout << name << endl;
}
C++11初始化赋值
int a = (15);
int b(20);
int a = {15};
int b{20};
在Linux平台,编译需要加-std=c++11参数
C++11的long long类型
在VS中,long是4字节,32位。 -2147483648~2147483647
在Linux中,long是8字节,64位。 -9223372036854775808~9223372036854775807
C++11标准增了long long类型的整数,至少64位,且至少与long一样长。
在VS中,long long是8字节,64位。 -9223372036854775808~9223372036854775807
在Linux中,long和long long类型都是8字节,64位。
ASCII
ASCII控制字符
十进制 | 符号 | 中文解释 | 十进制 | 符号 | 中文解释 |
---|---|---|---|---|---|
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 | 单元分隔符 |
ASCII显示字符
十进制 | 符号 | 中文解释 | 十进制 | 符号 | 中文解释 |
---|---|---|---|---|---|
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 | 删除 |
转义字符
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) |
C++11的原始字面量
格式:R"(字符串内容)"
使用R"(string\n)"
,可以取消字符串内容里包含的转义字符
如果是R"aa(string\n)bb"
,效果同上
std::cout << R"(string\r\t\n)" << std::endl;
字符串型
C++字符串:string 变量名 = "字符串内容" ;
C字符串:char 变量名[] = "字符串内容" ;
C风格字符串的本质是字符数组,C++风格字符串的本质是类,它封装了C风格字符串。
赋值:变量名 = "字符串内容" ;
拼接:变量名 = 变量名+ "字符串1" + "字符串2" + "字符串的内容n";
字符串比较:支持关系运算符
#include <iostream>
int main()
{
string str = "123";
if(str == "123")
std::cout << "true" << std::endl;
}
如果字符串内容都是常量,则不能使用加号(+)拼接
string str;
str = "123" + "string" + "abc"; // 不能这样使用
如果内容过长,可以多行书写
string str;
str = "123"
"string""abc";
/*
输出结果为
123stringabc
*/
自动类型转换
- 当表达式中出现了不同类型混合运算,取值范围低的类型将会自动向范围高的进行转换
- 当表达式中含有浮点型时,将会自动向浮点类型转换
- 赋值时,左右操作数类型不一致,右操作数自动转换为左操作数类型,然后赋值
- 赋值时,右操作数超出了左操作数的取值范围,将会把右操作数截断后赋值给左操作数
强制类型转换
(类型)表达式
类型(表达式)
double d = 12.59;
int i = (int)d;
int k = int(d);
/*
i和k的值为12
*/
指针
常量指针
声明格式:const type* variable;
- 不能通过
*variable
修改(可以访问)variable指向的内存空间存放的内容,但是可以通过原变量名进行修改 - 可以更改variable的内容(可以更改指针的指向)
int a = 3, b = 5;
const int* p = &a;
*p=4; // 不能这样改
a = 4; // 这样可以
p = &b; // ok
常量指针一般用于修饰函数的形参,表示不希望在函数内部修改形参指向的内存空间的内容
指针常量(引用)
声明格式:type* const variable;
- variable的值不可以改变,因此声明的时候必须初始化
- 可以通过
*variable
修改variable指向的内存空间的内容
常指针常量(常引用)
声明格式:const type* const variable;
- variable的值不可改变,不能通过*variable改变variable指向的内存空间的内容,但是可以访问
指针的输出
有时候C++不能识别出指针的内容是地址,因此可以这样输出(以整型变量的指针为例)
int a;
int* ptr = &a;
std:out << (int*)&a << std::endl;
std:out << (int*)ptr << std::endl;
CPP内存空间
程序运行时,内存主要分为四个区:栈、堆、数据段、代码段
-
栈:存放程序的局部变量、函数参数和返回值
-
堆:存放动态开辟内存的变量
-
数据段:存放程序中的全局变量和静态变量
-
代码段:存放可执行程序的二进制代码和常量
-
管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需手动释放,若程序中不释放,程序结束时由操作系统回收。
-
空间大小不同:堆内存的大小受限于物理内存空间;而栈就小得可怜,一般只有8M(可以修改系统参数)。
-
分配方式不同:堆是动态分配;栈有静态分配和动态分配(都是自动释放)。
-
分配效率不同:栈是系统提供的数据结构,计算机在底层提供了对栈的支持,进栈和出栈有专门的指令,效率比较高;堆是由C++函数库提供的。
-
是否产生碎片:对于栈来说,进栈和出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的分配和释放,会造成内存空间的不连续,容易产生碎片,太多的碎片会导致性能的下降。
-
增长方向不同:栈向下增长,以降序分配内存地址;堆向上增长,以升序分配内存地址。
动态分配内存
申请内存:new type(initval); // C++11支持{}
new type[size];
释放内存:delete address;
int* p = new int(5);
std::out << *p << std::endl; // 输出5
delete p;
p = NULL;
int* arr = new int[10]; // 申请10个int类型的内存空间
delete arr;
arr = NULL;
对空指针进行delete不会出错,系统会忽略该操作,内存释放后,应该将指针指向NULL
空指针
指针被delete之后,应该赋值为空指针NULL
或0
,C++11建议使用nullptr表示空指针,也就是(void*)0
Linux下如果使用nullptr,需要加-std=c++11
参数
野指针
指向未分配的内存空间的指针
规避野指针:
- 定义指针时,初始化为nullptr
- 使用delete释放后,赋值为nullptr
- 函数不返回局部变量地址
函数指针
函数的地址就是函数在内存中的起始地址
使用函数指针
- 声明函数指针
- 让函数指针指向函数的地址
- 通过函数指针调用函数
函数指针的声明和调用
声明格式:retval_type(*funptr)(type1, type2, typen);
C++调用:funptr(type1_val, type2_val, typen_val);
C调用:(*funptr)(type1_val, type2_val, typen_val);
一维数组
数组的声明
语法:type array[length];
length必须是整数,可以是常量,也可以是变量和表达式
C90规定必须用常量表达式指定数组大小,C99允许使用整型非常量表达式,Linux中可以使用变量
数组占用内存
使用sizeof(array)
函数可以得到数组占用空间大小(字节),但是只适用于C++的基本数据类型
数组的初始化
type array[length] = { val1, val2, val3, valn };
type array[] = { val1, val2, val3, valn };
type array[length] = { 0 };
type array[length] = {};
C++11可以不写等号
清空数组
void *memset(void *_Dst, int _Val, size_t _Size);
Linux下使用需要包含string.h头文件
复制数组
void *memcpy(void *_Dst, const void *_Src, size_t _Size);
Linux下使用需要包含string.h头文件
数组名是地址
数组名不一定会被解释为地址,使用sizeof(array)
时,返回array数组占用的内存字节数
数组名是常量,不可更改
数组名作为函数的参数
使用数组名/数组首地址作为函数的形参,函数有两种定义方式
retval_type fun(type* array_ptr);
retval_type fun(type array_ptr[]);
在函数内部,不要对指针array_ptr使用sizeof函数,它不是数组名
使用new动态创建一维数组
语法:type* array_ptr = new type[length];
释放:delete[]array_ptr
声明普通数组的时候,数组长度可以用变量,相当于在栈中动态创建数组,并且不需要释放
如果内存不足,调用new会产生异常,导致程序中止,如果在new后面加(std::nothrow)
则返回nullptr,不会产生异常,之后可以对指针判空,避免异常导致的崩溃
使用delete[]释放,不需要指定数组大小,系统会自动跟踪已经分配数组的内存
数组的排序
void qsort(void *_Base, size_t NumOfElements, size_t _SizeOfElement, _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction );
-
数组起始地址
-
数组元素个数
size_t
是unsigned long long
-
单个数组元素占用字节数
-
回调函数地址,回调函数应该符合
int compare(const void* p1, const void* p2)
-
如果compare函数的返回值<0,那么p1所指向元素会被排在p2所指向元素的前面
-
如果函数返回值==0,那么p1和p2的排序顺序不确定
-
如果函数返回值>0,那么p1所指向的元素会被排在p2所指向的元素的后面
int compare(const void* p1, const void* p2) { return *((int*)p1) - *((int*)p2); // 升序 return *((int*)p2) - *((int*)p1); // 降序 }
-
多维数组作为函数的参数
行指针
type (*ptr)[size]; // size is length
行指针是二维数组每行的首元素地址,每行有size个元素
int array1[3][4]; // array1是数组长度为3的整型数组的行地址
int (*p)[4] = array1; // p是每行首元素地址,每行有4个元素
int array2[2][3][4]; // array2是[3][4]二维数组的二维地址
int (*p)[3][4]; // p是每行首元素地址,每个元素都是一个[3][4]的二维数组
将多维数组传递给函数
// len是行地址的个数(数组有多少行)
void fun(int (*p)[3], int len);
void fun(int [][3], int len);
结构体
定义语法:
struct struct_name
{
member1_type member1;
member2_type member2;
membern_type membern;
};
struct struct_name
{
member1_type member1;
member2_type member2;
membern_type membern;
}alias; // alias为结构体struct_name的别名,声明结构体时可以使用alias struct_variable;
struct struct_name
{
member1_type member1;
member2_type member2;
membern_type membern;
}*alias_ptr;// alias_ptr为结构体struct_name*的别名,声明结构体时可以使用alias_ptr struct_variable;
- 结构体名是标识符
- 结构体成员可以是任意数据类型
- 定义结构体的代码可以放在任意位置(包括头文件)
- 结构体成员可以使用C++的类,但是不提倡
- C++中结构体中可以有函数指针,但是不提倡
- C++11中,定义结构体可以指定缺省值
结构体类型变量声明
struct struct_name struct_variable = { member1_val, member2_val, membern_val };
- C++11可以不写等于号
struct struct_name struct_variable = { 0 };
// C++11标准写法
struct_name* struct_variable;
*(struct_variable) = { member1_val, member2_val, membern_val };
// 定义的同时初始化
struct_name* struct_variable = new struct_name({ member1_val, member2_val, membern_val });
-
在C++中struct可以不写
-
定义结构体的时候可以创建结构体变量
struct struct_name { member1_type member1; member2_type member2; membern_type membern; };
-
使用
sizeof(结构体变量/结构体类型)
函数可以得到结构体变量占用的字节大小,结构体变量占用的空间不一定是各个成员变量的占用空间之和,因为存在字符对齐的情况#pragma pack(Bytes) // 如果Bytes为1,那么内存之间就没有空隙了,结构体变量占用空间必定为成员变量占用空间之和
每个编译器的内存对齐的规则不一样,在VS中缺省对齐是8 Bytes
清空结构体
void *memset(void *_Dst, int _Val, size_t _Size);
void bzero(void *s, size_t n);
复制结构体
可以使用memcpy()函数(只适用于C++基本数据类型)
也可以直接使用等于号(只适用于C++基本数据类型)
结构体中的指针成员变量
如果结构体中的指针指向的是动态分配的内存地址
- 对结构体变量使用
sizeof()
函数是无意义的 - 对结构体使用
memset()
可能造成内存泄露
string类中有一个是指向动态分配的内存地址的指针的
struct string
{
char* ptr; // 指向动态分配的内存地址
}
共用体
- 共用体能够存储不同的数据类型,但是同一时间只能存储其中的一种类型
- 共用体占用内存的大小是其最大的成员占用内存大小
- 全部成员使用一块内存
- 匿名共用体没有名字,匿名共同体在声明的时候就要创建变量
声明语法
union union_name
{
member1_type member1;
member2_type member2;
membern_type membern;
};
枚举
- 枚举默认情况下,第一个常量的值为0,后面的依次递增
声明语法
enum enum_name
{
enum_val1, // 0
enum_val2, // 1
enum_val3, // 2
enum_val4 = 5, // 5
enum_val5, // 6
enum_val6 // 7
}
可以使用enum_name创建枚举变量,该变量的值,只能是枚举类型包含的常量值
enum_name enum_variable = enum_val5;
可以将整数强制转换为枚举量
enum_name(integer_val)
引用
引用是C++所特有的概念
- 引用变量是C++新增的复合类型。
- 引用是已定义的变量的别名,因此可以如同使用变量一样使用引用
- 引用的主要用途是用作函数的形参和返回值。
- 引用的数据类型要与原变量名的数据类型相同。
- 必须在声明引用的时候初始化,初始化后不可改变。如果在初始化之后,再对引用赋值新的变量,其地址也不会改变,只会改变引用对应的变量的内容
- C和C++用&符号来指示/取变量的地址,C++给&符号赋予了另一种含义。
声明引用
type& reference_name = primary_variable;
使用引用
int primary_variable = 147;
int& reference_name = primary_variable;
std::cout << reference_name << std::endl;
std::cout << primary_variable << std::endl;
std::cout << &reference_name << std::endl;
std::cout << &primary_variable << std::endl;
int new_variable = 5;
reference_name = new_variable; // 相当于把new_variable的值赋给了reference_name或primary_variable
std::cout << &reference_name << std::endl; // 地址为primary_variable的地址
std::cout << &new_variable << std::endl; // 地址与reference_name和primary_variable的地址不同
std::cout << reference_name << std::endl; // 5
std::cout << primary_variable << std::endl; // 5
引用作为函数的参数
传递引用相当于传地址,但是使用引用比使用地址更好理解,引用形参对应的实参必须是变量
void fun(int& rv)
{
std::out << rv << std:endl;
}
int main()
{
int pv = 5;
fun(pv); // 将变量传进去
}
引用作为函数形参
如果一定要向引用传递常量,可以使用const关键字
void fun(const int& rv)
{
std::out << rv << std:endl;
}
int main()
{
fun(5); // 将常量5传进去,形参声明时使用了const关键字,编译器将会为常量5创建一个临时变量,引用的地址将会指向这个临时变量
}
使用const声明引用,并且赋值为常量,编译器将会为这个常量创建一个临时变量,引用的地址将会指向这个临时变量
const int& rv = 5;
// 等同于下面两句
int temp = 8;
const int& rv = temp;
引用作为函数返回值
函数的返回值一般会被拷贝到寄存器或者栈中,然后调用该函数的函数再使用这个值
引用作为函数返回值时
- 可以返回函数的引用形参、类的成员、全局变量、静态变量,不可以返回本函数局部变量的引用
- 返回引用的函数名是被引用变量的别名,将const用于引用的返回类型
- 返回类成员变量的引用时,可以使用const关键字声明接收返回值的引用,避免类成员变量被修改
// 返回函数的引用形参
int& fun2(int &rv)
{
rv += 1;
return rv;
}
int main()
{
int pv = 5;
int&rv = pv;
int& vv = fun2(rv);
std::cout << vv << std::endl;
}
// 返回静态变量
int& fun3(void)
{
static int sv = 4;
return sv;
}
int main()
{
int& rv = fun2();
std::cout << vv << std::endl;
}
// 函数名其实也是引用变量的别名
int& fun3(void)
{
static int sv = 4;
sv += 1;
std::cout << sv << std::endl; // 第一次输出5,第二次输出6
return sv;
}
int main()
{
int& rv = fun3();
fun3() = 10; // 调用一次fun3(),输出6,并且fun()是sv的引用,因此这一句也给sv赋值了10
std::cout << vv << std::endl; // 输出10
}
函数形参的默认值
可以使用如下语法声明函数的默认参数
void function(int a, char b = 'x', string c = "123456")
{
}
为形参指定默认值时,要注意以下几点
- 指定默认值的形参一定要在形参列表的末尾部分
- 不允许指定默认值的形参后面还有普通形参
- 调用函数时,必须为前n个形参传递实参,n(0~n)
函数重载
函数的重载其实就是函数多态
-
可以定义多个同名函数,他们的形参、形参的数据类型、形参的排序可以不同
-
使用重载函数,编译器将会自动匹配实参对应类型(可能会自动类型转换:低范围转换为高范围类型)的函数,如果有多个重载函数可以匹配,将会报错
-
重载函数只是在编写代码时看起来是同名的,实际上编译器在编译过程中会为重载函数重新命名,因此重载函数实际上并不是同名的
-
如果两个重载函数的形参列表中对应位置有同类型的变量和引用,如果实参是常量,将会匹配形参为变量的函数;如果实参是变量,这两个重载函数都会被匹配,此时编译出错
fun(int a){} fun(int& a){} int main() { int k = 4; fun(2); // 匹配第一个fun fun(k); // 两个都匹配 }
-
如果重载函数的形参列表中含有指定了默认值的形参,那么也可能出错
fun(int a, int b = 2, int c = 3){} fun(int a, int b = 3){} fun(int a){} int main() { fun(1); // 三个fun()都匹配 fun(1, 2); // 匹配前两个fun() fun(1, 2, 3); // 匹配第一个fun() }
-
如果仅对同名函数的形参使用const用于区分,则不能成为重载函数
// 这两者实际上还是一个函数,函数重名冲突 fun(const int a){} fun(int b)
内联函数(inline)
-
内联函数相当于把函数的函数体嵌入到调用其的函数中,调用几次嵌入几次
-
内联函数可以提高程序的运行速度(不再需要将实参和函数的地址拷贝到堆栈中了),但是比较占用内存
-
普通函数的声明和定义可以分开,内联函数的声明和定义是在一起的
-
如果内联函数过大,编译器将不会将其作为内联函数
-
内联函数不能递归
inline void fun()
{
int a = 1;
char b = 2;
std::cout << a << b << std::endl;
}
int main()
{
fun();
fun();
}
// 上面的main函数相当于下面的main函数
int main()
{
{
int a = 1;
char b = 2;
std::cout << a << b << std::endl;
}
{
int a = 1;
char b = 2;
std::cout << a << b << std::endl;
}
}
类
类的声明语法
class Class_name
{
public:
// 类的成员变量,也叫属性
member1_type member1;
member2_type member2;
membern_type membern;
// 类的成员函数,也叫方法,可以定义在类的外面
retval_type func1(type formal_param1, type formal_param2, type formal_paramn){}
retval_type func2(type formal_param1, type formal_param2, type formal_paramn){}
retval_type funcn(type formal_param1, type formal_param2, type formal_paramn){}
// 在类外定义的成员函数的声明语句
retval_type funcn_1(type formal_param1, type formal_param2, type formal_paramn);
private:
protected:
};
// 在类外面定义成员函数,而类中必须要有一个对应的声明语句
retval_type Class_name::funcn_1(type formal_param1, type formal_param2, type formal_paramn)
类的访问权限
类有三种访问权限,对应三个关键字
public
:类外可访问
private
:只有本类的成员函数可访问
protected
:用于类的继承
- 类的定义中
public
和private
可以出现多次 - 结构体的成员默认为public,类的成员默认为private
构造函数和析构函数
构造函数可以在对象实例化时,自动进行初始化(初始化属性)
析构函数可以在销毁对象前,自动进行清理(释放动态内存)
如果不提供构造函数和析构函数,编译器会提供空函数体的构造函数和析构函数
构造函数的定义
- 构造函数的访问权限必须是
public
- 函数名与类名相同
- 无返回值,不用写
void
- 可以有形参,可以重载,形参可以有默认值
- 没有形参的构造函数是默认构造函数,定义重载构造参数前,必须定义一个默认构造参数
- 创建对象时会自动调用,不能人为调用
- 在类内部使用构造函数后加括号
Class_name();
不是调用构造函数,而是创建匿名对象 - 如果成员变量中有指针,构造函数内一定要把指针初始化为
nullptr
,不然析构函数对其进行delelte时,可能delete野指针,导致程序崩溃
Class_name()
{
//TODO
}
析构函数的定义
- 析构函数的访问权限必须是
public
- 函数名为
~Class_name
- 无返回值,不用写
void
- 无形参,不能重载
- 销毁对象前自动调用一次,可以人为调用
类的使用
-
类的成员函数之间可以相互调用,也可以递归
-
成员函数可以重载
-
成员函数的形参可以使用默认值
-
类指针的用法和结构体指针用法相同
-
类的成员可以是任意类型
-
可以为类的属性指定默认值(C++11)
-
可以创建对象数组,如同其他类型的数组
-
对象可以作为实参传递给函数,一般传递引用
-
可以使用
new
和delete
实例化和销毁对象,也会自动调用构造和析构函数 -
在类的外部,一般不读写类的属性,而是使用成员函数对类的属性进行读写
-
对象一般不使用
memset()
清空属性,可以写一个专门清空属性的成员函数 -
一般不对类/对象使用
sizeof
(可以使用) -
使用结构体描述纯粹的数据,用类描述对象
-
如果在类中使用枚举,枚举的作用域是整个类,这样可以避免定义过多的常量
-
在类中定义的成员函数,都将自动成为内联函数,类外定义的如果没有
inline
关键字则不是内联函数retval_type Class_name::funcn_1(type formal_param1, type formal_param2, type formal_paramn) // 不是内联 inline retval_type Class_name::funcn_1(type formal_param1, type formal_param2, type formal_paramn) // 是内联
-
为了区分属性和成员函数的形参,在属性名前加
m_
前缀或_
前缀m_member1_type member1; _member1_type member1;
-
一般将声明类的代码放在头文件中,把成员函数定义的代码放在源文件中
-
不建议在构造函数/析构函数中写太多的代码,初始化成员变量的语句可以写在某个成员函数里
-
构造函数中最好只写初始化语句,或只会成功而不会失败的语句
-
如果类的成员也是类,那么应该先构造成员类,先析构成员类
class Class_name
{
public:
Class_name(){} // 必须定义一个无参数的构造函数(默认构造函数)
Class_name(int param)
{
}
~Class_name() // 析构函数
{
}
void fun()
{
}
};
// 隐式调用构造函数
Class_name cls;
Class_name cls();
Class_name cls(_member_val);
// 显示调用构造函数
Class_name cls;
cls = Class_name(); // Class_name()创建匿名对象,这一句将匿名对象的内容赋值给了对象cls
Class_name cls = Class_name(_member_val);
// 只有一个形参的构造函数,允许其使用赋值语句将其初始化为一个值
Class_name cls = 10 // param = 10
// 动态分配对象
Class_name* cls = new Class_name; // 实例化对象,会自动调用构造函数
cls->fun();
delete cls; // 销毁对象,会自动调用析构函数