目录
一、程序书写tips
C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。
使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查。
标准的 C++ 由三个重要部分组成:
- 核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。
- C++ 标准库,提供了大量的函数,用于操作文件、字符串等。
- 标准模板库(STL),提供了大量的方法,用于操作数据结构等。
C++ 支持多种编程风格。您可以使用 Fortran、C、Smalltalk 等任意一种语言的编程风格来编写代码。每种风格都能有效地保证运行时间效率和空间效率。
解决命令窗口一闪而过的方法:
- getchar();
- system("pause")
在 C++ 中,分号是语句结束符。也就是说,每个语句必须以分号结束
c++标识符以字母或下划线 _ 开始,后可跟数字
在 /* 和 */ 注释内部,// 字符没有特殊的含义。在 // 注释内,/* 和 */ 字符也没有特殊的含义。因此,您可以在一种注释内嵌套另一种注释。
前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。
整数常量也可以带一个后缀,U 表示无符号整数(unsigned),L 表示长整数(long),F表示浮点数..后缀可以是大写,也可以是小写,
使用多个文件且只在其中一个文件中定义变量时,可以使用 extern关键字在任何地方声明一个变量
c++允许在不同的块中重复定义变量,小块会隐藏大块的重复变量(Java语法不允许)
c++的引用符号&只能在定义变量的时候指定引用,在其他地方使用是取址的意思
exit(const),,表示终止程序,强制返回到操作系统,,,当const=0时,,正常退出,,,当const!=0时,,表示程序出现某种错误后退出头,包含的文件:“stdlib.h ” 或写 <cstdlib>
二、multi-sources处理
见C语言详解
三、关键字
asm | else | new | this |
auto | enum | operator | throw |
bool | explicit | private | true |
break | export | protected | try |
case | extern | public | typedef |
catch | false | register | typeid |
char | float | reinterpret_cast | typename |
class | for | return | union |
const | friend | short | unsigned |
const_cast | goto | signed | using |
continue | if | sizeof | virtual |
default | inline | static | void |
delete | int | static_cast | volatile |
do | long | struct | wchar_t |
double | mutable | switch | while |
dynamic_cast | namespace | template |
四、输入流和输出流
<iostream> | 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 |
(1)输出
cout << << <<;
endl 用于在行末添加一个换行符。
(2)输入
cin >> name >> age;
这相当于下面两个语句:
cin >> name;
cin >> age;
五、数据类型
(1)简单数据类型:
类型 | 关键字 |
---|---|
布尔型 | bool |
字符型 | char |
整型 | int |
浮点型 | float |
双浮点型 | double |
无类型 | void |
宽字符型 | wchar_t |
其实 wchar_t 是这样来的,所以 wchar_t 实际上的空间是和 short int 一样。
typedef short int wchar_t;
一些基本类型可以使用一个或多个类型修饰符进行修饰:
signed
unsigned
short
long
类型限定符提供了变量的额外信息。
限定符 | 含义 |
---|---|
const | const 类型的对象在程序执行期间不能被修改改变。 |
volatile | 修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
布尔常量共有两个,它们都是标准的 C++ 关键字:
- true 值代表真。
- false 值代表假。
我们不应把 true 的值看成 1,把 false 的值看成 0。(c++和Java都是不能等价,只有python可以等价)
(1)常量
在 C++ 中,有两种简单的定义常量的方式:
- 使用 #define 预处理器。
- 使用 const 关键字。
常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。
常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
如果字符常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L'x'),,此时它必须存储在 wchar_t 类型的变量中
否则,它就是一个窄字符常量(例如 'x'),,此时它可以存储在 char 类型的简单变量中。
(2)转义字符
转义序列 | 含义 |
---|---|
\\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
(2)容器数据类型
(1)枚举
如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
如果枚举没有初始化, 则从第一个标识符开始,从0开始递增,但是这种情况
enum color { red, green=5, blue };
在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
(2)字符串
c++里字符串不能使用+连接(与Java和python不同)
c++以分号作为结尾,一个语句可以写在多行,但是一串字符串想要写在多行必须用 \ 做下标记 (缺点是中间会空很大的空格)
C++ 提供了以下两种类型的字符串表示形式:
- C 风格字符串
- C++ 引入的 string 类类型
\0的ascii码是0,,一般空指针NULL指向的就是\0
(1)c风格字符串
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char greeting[] = "Hello";
#include <iostream>
using namespace std;
int main ()
{
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
cout << "Greeting message: ";
cout << greeting << endl;
return 0;
}
(2)String类字符串
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "Hello";
string str2 = "World";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 连接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// 连接后,str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}
String类字符串可以使用相加连接
(3)数组
固定大小的相同类型元素的顺序集合
(1)数组的定义:
double balance[10];
现在 balance 是一个可用的数组,可以容纳 10 个类型为 double 的数字。
(2)数组的初始化:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
如果您省略掉了数组的大小,数组的大小则为初始化时元素的个数。
局部变量:
int a[3]; //未初始化会得到随机的值
int a[3]={}; //这样写会自动初始化为0
全局变量:
int a[3]; //自动初始化为0
(3)多维数组:
type name[size1][size2]...[sizeN];
初始化:
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
(4)数组指针:
数组名表示数组首元素的地址,
double *p;
double balance[10];
p = balance;
一旦您把第一个元素的地址存储在 p 中,您就可以使用 *p、*(p+1)、*(p+2) 等来访问数组元素
注意:对于一个字符数组char a[10],如果执行
cout<<a<<endl;
会直接输出字符串,如果想要输出元素地址,需要赋值给指针后再强制类型转换(int *)
(5)指针数组
用于存储指针类型的数组
(6)数组的索引
数组通过 数组名[下标] 进行索引
(4)指针
概念 | 描述 |
---|---|
C++ Null 指针 | C++ 支持空指针。NULL 指针是一个定义在标准库中的值为零的常量。 |
C++ 指针的算术运算 | 可以对指针进行四种算术运算:++、--、+、- |
C++ 指针 vs 数组 | 指针和数组之间有着密切的关系。 |
C++ 指针数组 | 可以定义用来存储指针的数组。 |
C++ 指向指针的指针 | C++ 允许指向指针的指针。 |
C++ 传递指针给函数 | 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 |
C++ 从函数返回指针 | C++ 允许函数返回指针到局部变量、静态变量和动态内存分配。 |
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针,在c++中,NULL的地址是0
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:
ptr++
在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 个字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
(1)数组是常量指针
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++)
{
*var = i; // 这是正确的语法
var++; // 这是不正确的 数组是常量指针,,不能自增
}
return 0;
}
但是下面则是合法的
*(var + 2) = 500;
因为没有改变常量指针的值,,它表示把var[2]的值赋为500
(2)指针的指针
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号:
int **var; //声明了一个指向 int 类型指针的指针
访问二级指针也需要用两个取值符号*
(5)引用
引用相当于使两个变量共享一个内存上的值,其中任何变量都可以对内存的值做修改
而普通的赋值,两个变量并不指向同一块内存
(1)定义
int& r = i;
double& s = d;
(2)引用做函数的形参
#include <iostream>
using namespace std;
// 函数声明
void swap(int& x, int& y);
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;
}
// 函数定义
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
(3)引用做返回值
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i )
{
return vals[i]; // 返回第 i 个元素的引用
}
// 要调用上面定义函数的主函数
int main ()
{
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8; // 改变第 4 个元素
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
int& func() {
int q;
//! return q; // 在编译时发生错误
static int x;
return x; // 安全,x 在函数作用域外依然是有效的
}
(6)日期和时间
根据c++标准库去实现
(7)结构体
(1)定义
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
(2)结构体作为形参
相同模板的结构体可以直接赋值
void printBook( struct Books book );
// 声明一个结构体类型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
(3)指向结构体的指针
struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;
(3)数据类型的转换
当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值:
数据类型 | 初始化默认值 |
---|---|
int | 0 |
char | '\0' |
float | 0 |
double | 0 |
pointer | NULL |
sizeof()函数可以获得数据类型的大小
六、存储类型
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C++ 程序中可用的存储类:
- auto
- register
- static
- extern
- mutable
- thread_local (C++11)
从 C++ 17 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。
(1)auto
auto 关键字用于自动判断变量类型
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型
(2)register
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
{
register int miles;
}
定义 'register' 它意味着变量仅仅可能存储在寄存器中,这取决于硬件和实现的限制。
(3)static
使用 static 修饰局部变量生命期为整个程序,且只会初始化一次
使用static 修饰全局变量会使全局变量的作用域限制在声明它的文件内。(与extern相反)
使用static修饰类的成员变量时,表示该成员变量只有一份,被类保留
/*验证static修饰局部变量的效果*/
#include <iostream>
// 函数声明
void func(void);
static int count = 10; /* 全局变量 */
int main()
{
while(count--)
{
func();
}
return 0;
}
// 函数定义
void func( void )
{
static int i = 5; // 局部静态变量
i++;
std::cout << "变量 i 为 " << i ;
std::cout << " , 变量 count 为 " << count << std::endl;
}
********输出************
变量 i 为 6 , 变量 count 为 9
变量 i 为 7 , 变量 count 为 8
变量 i 为 8 , 变量 count 为 7
变量 i 为 9 , 变量 count 为 6
变量 i 为 10 , 变量 count 为 5
变量 i 为 11 , 变量 count 为 4
变量 i 为 12 , 变量 count 为 3
变量 i 为 13 , 变量 count 为 2
变量 i 为 14 , 变量 count 为 1
变量 i 为 15 , 变量 count 为 0
(4)extern
只能用于修饰全局变量
如果文件A想引用文件B定义的一个全局变量b,则需要在A中用extern重新声明一下
如果文件A想引用文件B定义的函数b().需要在A中用extern重新声明一下函数原型
extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示:
//第一个文件
#include <iostream>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
//第二个文件
#include <iostream>
extern int count;
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}
(5)mutable
仅适用于类的对象,它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
(6)thread_local
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
以下演示了可以被声明为 thread_local 的变量:
thread_local int x; // 命名空间下的全局变量
class X
{
static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
thread_local std::vector<int> v; // 本地变量
}
六、运算符
(1)优先级:
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
C++支持逻辑异或,符号也是^,只有相同的两个数取异或才是假,相异则为真
只有 赋值运算符 条件运算符 单目运算符 是从右向左的
c++的逻辑与或非与Java一样,和python不一样,,python的 and not or 比较奇特
(2)部分运算符的解释:
运算符 | 描述 |
---|---|
sizeof | sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 |
Condition ? X : Y | 条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 |
, | 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 |
.(点)和 ->(箭头) | 成员运算符用于引用类、结构和共用体的成员。 |
Cast | 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
& | 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 |
* | 指针运算符 * 指向一个变量。例如,*var; 将指向变量 var。 |
(1)逗号运算符
使用逗号运算符是为了把几个表达式放在一起。整个逗号表达式的值为系列中最后一个表达式的值。
从本质上讲,逗号的作用是将一系列运算按顺序从左到右执行。
最右边的那个表达式的值将作为整个逗号表达式的值,其他表达式的值会被丢弃(即incr的值)。例如:
var = (count=19, count+10, count+1); //结果var=20
在这里,首先把 count 赋值为19,,然后把 count 加10(但是没有赋值给count),最后,把最右边表达式 count+1 的值 20 赋给 var。上面表达式中的括号是必需的,因为逗号运算符的优先级低于赋值操作符。
(2)成员运算符
.(点)运算符和 ->(箭头)运算符用于引用类、结构和共用体的成员。
点运算符应用于实际的对象。而箭头运算符应用于指向对象的指针
struct Employee {
char first_name[16];
int age;
} emp;
##################################
strcpy(emp.first_name, "zara");
##################################
strcpy(p_emp->first_name, "zara"); //p_emp是指向对象emp的指针
(3)强制转换运算符
(type)是一个单目运算符
-
const_cast<type> (expr): const_cast 运算符用于修改类型的 const / volatile 属性。除了 const 或 volatile 属性之外,目标类型必须与源类型相同。这种类型的转换主要是用来操作所传对象的 const 属性,可以加上 const 属性,也可以去掉 const 属性。
-
dynamic_cast<type> (expr): dynamic_cast 在运行时执行转换,验证转换的有效性。如果转换未执行,则转换失败,表达式 expr 被判定为 null。dynamic_cast 执行动态转换时,type 必须是类的指针、类的引用或者 void*,如果 type 是类指针类型,那么 expr 也必须是一个指针,如果 type 是一个引用,那个 expr 也必须是一个引用。
-
reinterpret_cast<type> (expr): reinterpret_cast 运算符把某种指针改为其他类型的指针。它可以把一个指针转换为一个整数,也可以把一个整数转换为一个指针。
-
static_cast<type> (expr): static_cast 运算符执行非动态转换,没有运行时类检查来保证转换的安全性。例如,它可以用来把一个基类指针转换为派生类指针。
最简单的类型转换如下:
c = (int) a;
cout << "Line 1 - Value of (int)a is :" << c << endl ;
c = (int) b;
cout << "Line 2 - Value of (int)b is :" << c << endl ;
(4)&运算符
&做引用符号时,只能在定义变量的时候使用
七、控制流
(1)循环:
循环类型 | 描述 |
---|---|
while 循环 | 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 |
for 循环 | 多次执行一个语句序列,简化管理循环变量的代码。 |
do...while 循环 | 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 |
嵌套循环 | 您可以在 while、for 或 do..while 循环内使用一个或多个循环。 |
控制语句 | 描述 |
---|---|
break 语句 | 终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句。 |
continue 语句 | 引起循环跳过主体的剩余部分,立即重新开始测试条件。 |
goto 语句 | 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。 |
但是一般情况下,C++ 程序员偏向于使用 for(;;) 结构来表示一个无限循环。
可以按 Ctrl + C 键终止一个无限循环。
(2)分支
语句 | 描述 |
---|---|
if 语句 | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 |
if...else 语句 | 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 |
嵌套 if 语句 | 您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 |
switch 语句 | 一个 switch 语句允许测试一个变量等于多个值时的情况。 |
嵌套 switch 语句 | 您可以在一个 switch 语句内使用另一个 switch 语句。 |
八、函数
(1)简介
作用域是程序的一个区域,一般来说有三个地方可以定义变量:
-
在函数或一个代码块内部声明的变量,称为局部变量。
-
在函数参数的定义中声明的变量,称为形式参数。
-
在所有函数外部声明的变量,称为全局变量。
C++ 标准库提供了大量的程序可以调用的内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。
(2)函数的定义:
return_type function_name( parameter list )
{
body of the function
}
- 返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
- 函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
- 参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
- 函数主体:函数主体包含一组定义函数执行任务的语句。
(3)形参表
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值赋值给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 |
指针调用 | 该方法把参数的地址赋值给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
引用调用 | 该方法把参数的引用赋值给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
// 指针调用
void swap(int *x, int *y) //把变量的地址传过来
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 x 赋值给 y */
return;
}
swap(&a, &b);
// 引用调用
void swap(int& x, int& y) //把实参的引用传递过来
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
swap(a, b);
c++的形参表可以指定默认值(与python类似)
int sum(int a, int b=20) //当实参缺省b时,按照默认值赋值
{
int result;
result = a + b;
return (result);
}
(4)Lambda 函数
C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。
(1)定义
[函数对象参数] (函数形参) mutable 或 exception 声明 -> 返回值类型 {函数体}
auto pfun=[](){cout<<"hello world"<<endl;} //这里使用auto自动判断返回值类型
pfun(); //调用这个函数
(2)函数对象参数(不可省略)
- [] 不截取任何变量
- [&] 截取外部作用域中所有变量,并作为引用在函数体中使用
- [=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
- [=, &foo] 截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对foo变量使用引用
- [var] 截取var变量并且拷贝一份在函数体重使用,同时不截取其他变量
- [this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。
(3)返回值类型
"->返回值类型"可以省略,,程序可以自动判断返回值类型
(4)修饰符
- mutable 表示函数体可以修改捕获变量的,同时可以访问捕获对象的非常属性成员函数
- exception说明lambda表达式是否抛出异常以及何种异常
- attribute用来声明属性
(5)c++的随机数
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
int main ()
{
int i,j;
// 设置种子
srand( (unsigned)time( NULL ) );
/* 生成 10 个随机数 */
for( i = 0; i < 10; i++ )
{
// 生成实际的随机数
j= rand();
cout <<"随机数: " << j << endl;
}
return 0;
}
(6)指针作为函数的传递参数:
函数的形参可以采用以下三种形式:
方式一
void myFunction(int *param)
{
.
.
.
}
方式二
void myFunction(int param[10])
{
.
.
.
}
方式三
void myFunction(int param[])
{
.
.
.
}
函数实参则为内存块首地址
(7)指针作为函数的返回值
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
// 要生成和返回随机数的函数
int * getRandom( )
{
static int r[10];
// 设置种子
srand( (unsigned)time( NULL ) );
for (int i = 0; i < 10; ++i)
{
r[i] = rand();
cout << r[i] << endl;
}
return r; //返回数组首地址,供调用者操作
}
// 要调用上面定义函数的主函数
int main ()
{
// 一个指向整数的指针
int *p;
p = getRandom();
for ( int i = 0; i < 10; i++ )
{
cout << "*(p + " << i << ") : ";
cout << *(p + i) << endl;
}
return 0;
}
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
(8)函数指针与回调函数
见C语言知识详解
九、面向对象基础
(1)类的定义
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
Box Box1; // 对象 Box1,类型为 Box
Box Box2; // 对象 Box2,类型为 Box
需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。我们将在后续的教程中学习如何访问私有成员和受保护的成员。
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义:
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void); //必须要在类内部声明原型
}; //分号
double Box::getVolume(void)
{
return length * breadth * height;
}
(2)访问修饰符
一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private。
class Base {
public:
// 公有成员
protected:
// 受保护成员
private: //默认
// 私有成员
};
(1)public
公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值
(2)private
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类内和友元函数可以访问私有成员。
实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数,
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <<endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的
box.setWidth(10.0); // 使用成员函数设置宽度
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
(3)protect
保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
(3)类的三种继承方式
-
1.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
-
2.protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
-
3.private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
如果继承时不显示声明是 private,protected,public 继承,则默认是 private 继承,在 struct 中默认 public 继承:
(4)构造函数
- 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
- 构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,需要在不同的字段使用逗号进行分隔,如下所示:
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
....
}
相当于 X=a;
Y=b;
Z=c;
(5)析构函数
类的析构函数是类的一种特殊的成员函数,每删除一个对象便执行一次
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数:
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
(6)拷贝构造函数
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数
#include <iostream>
using namespace std;
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj) //拷贝构造函数,专用于对象的拷贝
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int; //c++定义指针的方式
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr; //在析构函数里删除指针释放内存
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( )
{
Line line1(10);
Line line2 = line1; // 这里也调用了拷贝构造函数
display(line1);
display(line2);
return 0;
}
*****************************
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存 //定义在函数里的类,作用域和生命期也在函数里
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存
释放内存 //每删除一个对象,执行一次析构函数
(7)友元函数
友元函数可以定义在类的内部或外部,但必须在类里声明
友元函数不属于类成员,所以不存在this指针,可以不通过类而调用函数
友元函数有权访问类的所有私有(private)成员和保护(protected)成员
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元
(1)函数原型
class Box
{
double width;
public:
double length;
friend void printWidth( Box box ); 友元函数声明在类的内部,但不属于类的成员函数,没有this指针
friend class ClassTwo; 类ClassTwo的所有成员都将是Box的友元
void setWidth( double wid );
};
(2)粒子
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );
friend class BigBox;
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
//友元类
class BigBox
{
public :
void Print(int width, Box &box)
{
// BigBox是Box的友元类,它可以直接访问Box类的任何成员
box.setWidth(width);
cout << "Width of box : " << box.width << endl;
}
};
// 程序的主函数
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
(8)内联函数
在类内定义的函数都是内联函数,即使没有使用 inline 说明符。
普通函数或者在类外定义的成员函数:
inline int Max(int x, int y)
{
...
}
inline void ClassName:: funname(){
...
}
我们可以在声明函数和定义函数二者都写inline,也可以只在函数声明时加inline,而定义函数时不加inline。只要在调用该函数之前把inline的信息告知编译系统,编译系统就会在处理函数调用时按内联函数处理
内联函数的注意事项:
- 内联函数不能包括复杂的控制语句,如循环语句和switch语句;
- 只将规模很小(一般5个语句一下)而使用频繁的函数声明为内联函数。在函数规模很小的情况下,函数调用的时间开销可能相当于甚至超过执行函数本身的时间,把它定义为内联函数,可大大减少程序运行时间。
(9)this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址
友元函数没有 this 指针,因为友元不是类的成员
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume(); //this是Box1的指针
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2)) //因为是Box1对象的成员,所以上面的this是Box1的指针
{
cout << "Box2 is smaller than Box1" <<endl;
}
else
{
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
(10)指向对象的指针
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
Box *ptrBox; // Declare pointer to a class.
// 保存第一个对象的地址
ptrBox = &Box1; //需要对对象取地址
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
// 保存第二个对象的地址
ptrBox = &Box2;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box2: " << ptrBox->Volume() << endl;
return 0;
}
(11)static成员变量
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本,可以被该类的对象访问和修改(和Java一样),访问方式如下,效果一样
- <类名>::<static成员变量>
- <对象名>.<static成员变量>
不能把静态成员的初始化放置在类的定义中,但是可以在类外通过使用范围解析运算符 :: 来对它进行初始化
static变量必须要有类外的初始化,否则编译报错,可以不指定具体的初始化值
static时存储类型的修饰符,static变量存储在静态存储区
在一个类内声明两个相同的标号,但是它们的存储区不同,所以是两个独立的变量
public:
static int objectCount;
int objectCount = 0; //局部变量存在动态存储区,缺省修饰符auto
类的非静态成员变量在类内声明时不必初始化,此时它们是垃圾值,一般在创建对象时通过构造函数初始化非静态成员变量
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount; 声明,不能再这里初始化
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 长度 非静态成员变量声明
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount ; //没有指定初始化的值(自动为0),但这一步必须有,别加static
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
(12)static成员函数
静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问
静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
静态成员函数不属于任何对象,但可以通过对象访问
- <类名>::<static成员函数>
- <对象名>.<static成员函数>
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount() //静态成员函数可以在类内定义,也可以在类外定义
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
(13)类的继承
继承的默认访问修饰符是private
// 基类
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape //若为缺省,则是private
{
public:
int getArea()
{
return (width * height);
}
};
访问 | public | protected | private |
---|---|---|---|
同一个类 | yes | yes | yes |
派生类 | yes | yes | no |
外部的类 | yes | no | no |
子类无法继承父类的:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
c++的广度多继承:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
十、重载
重载不是覆盖,重载只是让数据自己去选择传入哪个函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
(1)函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
(2)运算符重载
- C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载;
- 运算重载符不可以改变操作数的个数。
- 运算重载符不可以改变优先级。
- 运算重载符不可以改变结合性。
- 运算符重载本质上也是函数调用
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length; //this指Box1
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2; //相当于Box1.operator+(Box2) //访问实例Box1的operator+()方法
可重载的运算符列表:
双目算术运算符 | + (加),-(减),*(乘),/(除),% (取模) |
关系运算符 | ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),--(自减) |
位运算符 | | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[](下标) |
不可重载的运算符列表:
- .成员访问运算符
- .*, ->*成员指针访问运算符
- ::域运算符
- sizeof长度运算符
- ?:条件运算符
- #:预处理符号
(1)自增和自减的重载
#include <iostream>
using namespace std;
class Time
{
private:
int hours; // 0 到 23
int minutes; // 0 到 59
public:
// 所需的构造函数
Time(){
hours = 0;
minutes = 0;
}
Time(int h, int m){
hours = h;
minutes = m;
}
// 显示时间的方法
void displayTime()
{
cout << "H: " << hours << " M:" << minutes <<endl;
}
// 重载前缀递增运算符( ++ )
Time operator++ () //这个是++num前缀自增,默认是前缀
{
++minutes; // 对象加 1
if(minutes >= 60)
{
++hours;
minutes -= 60;
}
return Time(hours, minutes); //不建议这种写法 .这是直接调用了构造函数,生成了一个无名对象
}
// 重载后缀递增运算符( ++ )
Time operator++( int ) //这个是num++,后缀自增,int只是用来标志后缀自增的
{
// 保存原始值
Time T(hours, minutes); //递归式内部类
// 对象加 1
++minutes;
if(minutes >= 60)
{
++hours;
minutes -= 60;
}
// 返回旧的原始值
return T;
}
};
int main()
{
Time T1(11, 59), T2(10,40);
++T1; // T1 加 1 //相当于T1.operator++ ()
T1.displayTime(); // 显示 T1
++T1; // T1 再加 1
T1.displayTime(); // 显示 T1
T2++; // T2 加 1
T2.displayTime(); // 显示 T2
T2++; // T2 再加 1
T2.displayTime(); // 显示 T2
return 0;
}
(2)输入输出运算符重载
在c++里,输入输出是运算符 而不是函数
cin和cout分别是istream类和ostream类的对象
应用于基本类型的输入、输出操作都已经在C++标准库中定义好,没有必要重新定义,也不允许重新定义。而对于用户自定义类来说,如果想利用输入、输出操作符进行本类对象的输入、输出操作,就需要对<<和>>操作符进行重载。
- istream & operator >> (istream &, 自定义类 &);
- ostream & operator << (ostream &, 自定义类 &);
(1)特殊重载方式
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance(){
feet = 0;
inches = 0;
}
Distance(int f, int i){
feet = f;
inches = i;
}
friend ostream& operator<<( ostream& output, //这个参数传入的是cout本身
const Distance& D ) //这个参数传入用户自定义数据类型
{
output << "F : " << D.feet << " I : " << D.inches;
return output; //返回值是cout,以保证级联输出
}
friend istream& operator>>( istream& input, Distance& D ) //必须定义成友元函数
{
input >> D.feet >> D.inches; 输入在这里完成
return input;
}
};
int main()
{
Distance D1(11, 10), D2(5, 11), D3;
cout << "Enter the value of object : " << endl;
cin >> D3;
cout << "First Distance : " << D1 << endl;
cout << "Second Distance :" << D2 << endl;
cout << "Third Distance :" << D3 << endl;
/***cout<<"abc"相当于operator<<(cout,"abc"),operator<<是函数名,这就是
要定义成友元函数的原因,调用函数后,返回值依旧是cout,可以进行下一级的输出cout<<D3,编译器根据输入的
数据类型判断调用标准函数还是重载函数,所以运算符重载函数的针对性也非常强***/
return 0;
}
(2)一般重载方式
这种输出也很有意思
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance(){
feet = 0;
inches = 0;
}
Distance(int f, int i){
feet = f;
inches = i;
}
ostream& operator<<( ostream & os)
{
os<<"英寸:"<<feet<<"\n英尺:"<<inches;
return os;
}
};
int main ()
{
Distance d1(20,18);
d1<<cout;//相当于d1.operator<<(cout) 表现的像是二元运算符 +
}
(3)赋值运算符的重载
和加法的重载一致
(4)数据类型的隐式转换
#include<iostream>
using namespace std;
class Int {
private:
int n;
public:
Int(int i) { 在本类内部创建对象
n = i;
}
operator int() // 这里就是隐式转换声明,应注意到它与运算符重载的不同之处,缺省表示返回类型是int
{
return n;
}
};
int main()
{
Int a(5); //a是个对象类型
int c = a; // 隐式调用转换函数
cout << c << endl;
cout << a << endl; //
}
(5)函数调用运算符 () 重载
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
// 所需的构造函数
Distance() {
feet = 0;
inches = 0;
}
Distance(int f, int i) {
feet = f;
inches = i;
}
// 重载函数调用运算符
Distance operator()(int a, int b, int c)
{
Distance D;
// 进行随机计算
D.feet = a + c + 10;
D.inches = b + c + 100;
return D;
}
// 显示距离的方法
void displayDistance()
{
cout << "F: " << feet << " I:" << inches << endl;
}
};
int main()
{
Distance D1(11, 10), D2;
cout << "First Distance : ";
D1.displayDistance();
D2 = D1(10, 10, 10); // 相当于 D2=D1.operator()(10, 10, 10);
cout << "Second Distance :";
D2.displayDistance();
return 0;
}
(6)下标运算符 [] 重载
#include <iostream>
using namespace std;
const int SIZE = 10;
class safearay
{
private:
int arr[SIZE];
public:
safearay()
{
register int i;
for(i = 0; i < SIZE; i++)
{
arr[i] = i;
}
}
int& operator[](int i)
{
if( i > SIZE )
{
cout << "索引超过最大值" <<endl;
// 返回第一个元素
return arr[0];
}
return arr[i];
}
};
int main()
{
safearay A;
cout << "A[2] 的值为 : " << A[2] <<endl; //相当于A.operator[](2)
cout << "A[5] 的值为 : " << A[5]<<endl;
cout << "A[12] 的值为 : " << A[12]<<endl;
return 0;
}
(7) 类成员访问运算符 -> 重载
class Ptr{
//...
X * operator->(); 重载运算符->
};
void f(Ptr p ) //p是个对象
{
p->m = 10 ; // 解释为(p.operator->())->m = 10
}
(8)关系运算符重载
与加法理解一致
十、接口与数据封装
//抽象把代码分离为接口和实现
#include <iostream>
using namespace std;
class Adder{
public:
// 构造函数
Adder(int i = 0)
{
total = i;
}
// 对外的接口
void addNum(int number)
{
total += number;
}
// 对外的接口
int getTotal()
{
return total;
};
private:
// 对外隐藏的数据
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total " << a.getTotal() <<endl;
return 0;
}
公有成员 addNum 和 getTotal 是对外的接口,用户需要知道它们以便使用类。私有成员 total 是用户不需要了解的,但又是类能正常工作所必需的。
十、文件操作
十一、异常处理
十二、申请内存空间(new和delect)
(1)new
new其实就是告诉计算机开辟一段新的空间
new开辟的空间在堆上,而一般声明的变量存放在栈上
- new int; //开辟一个存放整数的存储空间,
- new int(100); //开辟一个存放int型数据的空间,并指定该整数的初值为100,这是个int类的匿名对象,有参初始化
- new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
- new int[5][4]; //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址
- float *p=new float (3.14159); //开辟一个存放单精度数的空间,并指定该实数的初值为3.14159
- Box* myBoxArray = new Box[4]; //对象的动态内存分配,数组每个元素都是Box类型,把类看作用户自定义数据类型
用new分配数组空间时不能指定初值。
申请成功则返回空间首地址,,申请失败会返回一个空指针NULL
(2)delete
释放内存用:
- delete <空间首地址>
- delete [] array;//在指针变量前面加一对方括号,表示是对数组空间的操作
//三阶指针
int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为h
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
array[i] = new int *[n];
for( int j=0; j<n; j++ )
{
array[i][j] = new int [h];
}
}
//释放
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete[] array[i][j];
}
delete[] array[i];
}
delete[] array;