引用
引用:给一个已有对象取一个别名
就是复制变量的源地址
1
2
3
4
|
int a = 100;
int &ra = a;
a = 200;
ra = 200; //a与ra等价
|
注意点:
- 引用必须在定义的同时赋值,不可单独定义引用,例如
int &r; 是错误的。
- 引用一方面提高了数据传输的效率,另一方面简化了数据表达的样式(跟指针相比)
类型转换
旧式类型转换
1
2
|
(char)a; // C语言风格类型转换
char(a); // C++风格类型转换
|
新式类型转换
1
2
3
|
const_cast<char *>(a);
static_cast<char *>(a);
dynamic_cast<char *>(a);
|
- const_cast : 专用于去除指针或引用的 const 属性
- static_cast : 与旧式转换相近,但提供了更易于查找的语法,并能有效识别不兼容类型。
- dynamic_cast : 专用于类类型的上下代际间的转换
新式转换const_cast
- const_cast 旨在去除标识符的cv限定属性(即 const 与 volatile)
- const_cast 只能作用于指针或引用类型
1
2
3
4
5
6
7
8
9
10
11
|
int main()
{
int i = 6;
const int &ri = i; //const 引用,数据不可修改
//错误示范
ri = 8;
//正确的
const_cast<int &>(ri) = 8;
}
|
处理常目标指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
int main()
{
int i = 6;
const int *p = &i; //常量指针,不可修改目标
//错误
*p = 8;
//正确,去除const特性后,可以修改
*(const_cast<int *>(p)) = 8;
// 错误,试图扩大权限
int *k = p;
// 正确,去除 const 特性后,可赋值给普通指针 k
int *k = const_cast<int *>(p);
}
|
虽然 const_cast 可以将常目标指针的 const 属性剔除掉,但它不能剔除普通变量和常指针本身的const 属性。
1
2
3
4
5
6
7
8
9
10
11
12
|
int main()
{
int i=6;
int *const p = &i; //指针常量
int j=9;
//错误
p = &j;
//错误,const_cast不能去除常量指针的const属性
const_cast<int *>(p) = &j;
}
|
新式转换 static_cast
static 意味着静态转换,静态的含义是操作的过程只发生在编译阶段,而不是运行阶段,静态转换不涉及类型推理。
用法:
增加可读性
1
2
3
4
5
6
7
8
9
10
11
12
|
int main()
{
float f = 314;
//旧式
int i = (int) f;
int i = int(f);
//新式,等价于旧式转换
//新式静态强转具有可读性,容易查找
int i = static_cast<int>(f);
}
|
提高安全性
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int main()
{
int i = 6; // 普通整型数据
float *pf; // 普通浮点指针
// 旧式转换不进行任何合理性检查
// 下面的代码,可以将类型问题瞒天过海
// 编译器让其畅行无阻,开发者肉眼也难以察觉
pf = (float *)&i; // float * 与 int * 不兼容,照样通过编译
// 使用 static_cast 遇到非兼容性类型转换,会提出警告甚至错误
pf = static_cast<float *>(&i); // float * 与 int * 不兼容
}
|
注意,虽然 static_cast 可以将不兼容的普通数据类型转换明确指出来(报错),但对于代际类类型的上下层转换无能为力,对于类类型的代际上行转换或下行转换,需要借助 dynamic_cast 去保证运行的安全性。
关键字auto
在C语言中,auto 用来声明一个存储在栈的变量,因此它只能用来修饰临时变量,不能用来修饰全 局变量、静态变量,与此同时临时变量本身默认就是存储在栈中,因此在C语言中,auto 基本上是作废的。 在C++中,auto 代表自动获得数据的类型,比如:
1
|
auto a = 100; // 等价于: int a = 100;
|
使用auto 关键字可以对具体的类型进行模糊(泛化),如果某一个功能的操作流程式固定,唯一 的不同在于数据的类型,这时候就可以简单地使用auto关键字对类型进行模糊的操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;
// 通过auto简单粗暴地模糊数据的类型
// 让该函数可以接收任意类型的参数传递
auto Add(auto a, auto b)
{
return a + b;
}
int main(int argc, char const *argv[])
{
int Num = Add(123, 567);
cout << Num << endl;
float Num2 = Add(123.333, 567.222);
cout << Num2 << endl;
string s1 = "Hello";
string s2 = "World";
auto str = Add(s1, s2);
cout << str << endl; // 这里修复:en → endl
return 0;
}
|
函数默认参数
C++允许一个函数的参数有默认值,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#include <iostream>
using namespace std;
// 函数的声明
// 右侧的参数 b 如果没有传递,则默认值为3.14
void f(int a, float b = 3.14)
{
cout << "a = " << a << ", b = " << b << endl;
}
int main()
{
// 显式给 b 传递参数1.23
f(100, 1.23);
// 使用 b 的默认值3.14
f(200);
return 0;
}
|
- 为了防止出现二义性,具备默认值的参数只能位于参数列表的最右边
- 如果函数定义与函数声明不在同一文件,一般只为声明指定默认参数,定义处不写默认参数。
函数重载
就是相同的函数名,有不同的参数。
在C语言中,相同作用域内的重名函数会引起冲突,哪怕它们拥有不同的参数列表也不行,而在 C++中,重名函数只要满足一定的要求,可以同时存在,这大大拓展了函数设计的灵活性。这种重名且可并存的语法机制,被称为函数重载。
C++中大量存在重载的函数,这些同名函数实际上就是一个函数的不同版本。
1
2
3
4
5
|
string ( const string& str );
string ( const string& str, size_t pos, size_t n = npos );
string ( const char * s, size_t n );
string ( const char * s );
string ( size_t n, char c );
|
这些名称相同的函数,都存在于同一名空间中(作用域),怎样才能形成合理合法的重载而不会冲突呢?这个问题的答案比较复杂,总体的基本原则是,只要让系统在使用它们的时候,可以有效地区分各个不同的版本,那它们就可以形成合法的重载版本。
可以重载的情形
- 参数个数不同(注意默认参数)
- 参数类型不同
- 参数列表的顺序不同
- 类方法(即类内部的函数)的 const 属性可以构成重载
- 普通指针与常目标指针可以构成重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 参数个数不同,可以形成重载
void f1(int a);
void f1(int a, int b);
// 参数类型不同,可以形成重载
void f2(int a);
void f2(float b);
class A
{
// 类方法的const属性,可以形成重载
void f3() const;
void f3();
};
// 普通指针与常目标指针,可以形成重载
void f4( char *p);
void f4(const char *p);
|
不可以重载的情形
- 函数名、函数参数列表完全一致。
- 函数的返回值类型差异。
- 静态函数声明(static)。
- const 型变量(包括常指针)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 函数名、参数列表完全一致,仅靠返回值类型的差异,将无法形成重载,这两个函数将会冲突
void f1(int a);
float f1(int a);
// static不能形成重载,以下两个函数将会冲突
int f2(int a);
static int f2(int a);
// const型变量(包括常指针),不能形成重载
void f3( int a);
void f3(const int a);
void f4(char *p);
void f4(char * const p);
|
引用形参
引用类型的参数是否可以形成重载,要根据实参的具体情况来定:
- 如果实参为常量,那么可以重载。
- 如果实参为变量,那么不可以重载。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void f(int a);
void f(int &a);
int main()
{
int m = 100;
// ×:会引起二义性,两个版本都能跟参数完全匹配
f(m);
// √:可以顺利调用,因为普通引用类型无法指向常量
f(6);
}
|
引用类型的参数会很容易引起冲突,因此一般而言不要把引用作为重载的条件。
堆内存管理
在 C 语言中,使用 malloc() 和 free() 来分配和释放堆内存,这两个函数在 C++ 中也一样可用,但 C++ 有更好的 API 来实现这一功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
int main(void)
{
// 申请一块int型数据大小的堆内存
int *p1 = new int;
int *pp1 = malloc(sizeof(int));
// 申请一块可以存储100个int型数据大小的堆内存
int *p2 = new int[100];
int * pp2 = calloc(100 , sizeof(int));
// 申请一块35个字节大小的堆内存
char *p3 = new char[35];
char * pp2 = calloc(35 , sizeof(char));
// 释放所有的堆内存资源
delete p1;
delete [] p2;
delete [] p3;
free(pp1);
free(pp2);
free(pp3);
}
|
从以上代码可以看出,new 和 delete 的语法是很简单的,唯一需要注意的是,当分配和释放多个 连续内存块(数组类型的),需要加上方括号 [ ]
与 C 语言的 malloc() 和 free() 一样,new 和 delete 只能用来管理堆内存,不能用来操作栈内存、静态内存等其他内存空间,且在释放内存的时候只能一次性释放完毕(而且释放的目标参数必须是首地址),不允许释放一部分。
可以看到,在操作基本数据类型的时候,这两组堆内存操作函数是没有区别的,它们真正的区别在给类对象分配内存的时候,具体而言指的是:
- 申请类类型的内存时,
new 会自动调用类的构造函数,而 malloc() / calloc() 不会。
- 释放类类型的内存时,
delete 会自动调用类的析构函数,而 free() 不会。
枚举循环
从C++11开始,C++对for语句引入了枚举循环的概念,专用于具有确定元素个数的数组、容器或集合
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int a[3] = {1,2,3};
// 枚举数组a中的各个元素,并输出它们
for(int i : a)
cout << i << endl;
return 0;
}
|
循环的过程就是让变量 i 逐个取得数组 a 中的各个元素
循环次数不再由程序代码指定, 而是由待枚举的数据的元素个数决定