类和对象
C语言中的结构体:
1 | #include <stdio.h> |
C++中的类也是一种构造类型,但是进行了一些扩展,类的成员不但可以是变量,还可以是函数;通过类定义出来的变量也有特定的称呼,叫做“对象”。
1 | #include <stdio.h> |
C语言中的 struct 只能包含变量,而C++中的 class 除了可以包含变量,还可以包含函数。
命名空间
命名空间主要是用来解决合作开发时的命名冲突问题,有时也被称为名字空间、名称空间。
语法格式为:
1 | namespace name{ |
举例:
1 | namespace Li{ //小李的变量定义 |
使用:
1 | Li :: fp = fopen("one.txt", "r"); //使用小李定义的变量 fp |
是一个新符号,称为域解析操作符,在C++中用来指明要使用的命名空间。
除了直接使用域解析操作符,还可以采用 using 声明,例如:
1 | using Li :: fp; |
在代码的开头用using
声明了 Li::fp,它的意思是,using 声明以后的程序中如果出现了未指明命名空间的 fp,就使用 Li::fp;但是若要使用小韩定义的 fp,仍然需要 Han::fp。
using 声明不仅可以针对命名空间中的一个变量,也可以用于声明整个命名空间,例如:
1 | using namespace Li; |
如果命名空间 Li 中还定义了其他的变量,那么同样具有 fp 变量的效果。在 using 声明后,如果有未具体指定命名空间的变量产生了命名冲突,那么默认采用命名空间 Li 中的变量。
命名空间内部不仅可以声明或定义变量,对于其它能在命名空间以外声明或定义的名称,同样也都能在命名空间内部进行声明或定义,例如类、函数、typedef、#define 等都可以出现在命名空间中。
1 |
|
标准库和std命名空间
- iostream.h:用于控制台输入输出头文件。
- fstream.h:用于文件操作的头文件。
- complex.h:用于复数计算的头文件。
和C语言一样,C++ 头文件仍然以.h
为后缀,它们所包含的类、函数、宏等都是全局范围的。
后来 C++ 引入了命名空间的概念,计划重新编写库,将类、函数、宏等都统一纳入一个命名空间,这个命名空间的名字就是std
。std 是 standard 的缩写,意思是“标准命名空间”。
为了避免头文件重名,新版 C++ 库也对头文件的命名做了调整,去掉了后缀.h
,所以老式 C++ 的iostream.h
变成了iostream
,fstream.h
变成了fstream
。而对于原来C语言的头文件,也采用同样的方法,但在每个名字前还要添加一个c
字母,所以C语言的stdio.h
变成了cstdio
,stdlib.h
变成了cstdlib
。
可以发现,对于不带.h
的头文件,所有的符号都位于命名空间 std 中,使用时需要声明命名空间 std;对于带.h
的头文件,没有使用任何命名空间,所有符号都位于全局作用域。这也是 C++ 标准所规定的。
不过现实情况和 C++ 标准所期望的有些不同,对于原来C语言的头文件,即使按照 C++ 的方式来使用,即#include <cstdio>
这种形式,那么符号可以位于命名空间 std 中,也可以位于全局范围中,请看下面的两段代码。
- 使用命名空间 std:
1 | #include <cstdio> |
- 不使用命名空间 std:
1 | #include <cstdio> |
大部分编译器在实现时并没有严格遵循标准,它们对两种写法都支持,程序员可以使用 std 也可以不使用。第 1) 种写法是标准的,第 2) 种不标准.
在 main() 函数中声明命名空间 std,它的作用范围就位于 main() 函数内部,如果在其他函数中又用到了 std,就需要重新声明,请看下面的例子:
1 |
|
如果希望在所有函数中都使用命名空间 std,可以将它声明在全局范围中。
1 |
|
将 std 直接声明在所有函数外部,这样虽然使用方便,但在中大型项目开发中是不被推荐的,这样做增加了命名冲突的风险,推荐在函数内部声明 std。
C++输入输出
在C++语言中,C语言的这一套输入输出库我们仍然能使用,但是 C++ 又增加了一套新的、更容易使用的输入输出库。
1 |
|
使用 cout 进行输出时需要紧跟<<
运算符,使用 cin 进行输入时需要紧跟>>
运算符,这两个运算符可以自行分析所处理的数据类型,因此无需像使用 scanf 和 printf 那样给出格式控制字符串。其中endl
表示换行。
cin 和 cout都支持连续输入和输出
1 | cin>>x>>y; |
C++ 与 C 的改进
C 规定,所有局部变量都必须定义在函数开头,C99 标准取消这这条限制,但是 VC/VS 对 C99 的支持很不积极,仍然要求变量定义在函数开头。但是C++ 取消了原来的限制,变量只要在使用之前定义好即可,不强制必须在函数开头定义所有变量,VC/VS 下就不会报错。
C++ 新增了bool
类型,它一般占用 1 个字节长度。bool 类型只有两个取值,true 和 false。但是输出 bool 变量的值时还是用数字 1 和 0 表示。
const关键字
定义const对象
const修饰符可以把对象转变成常数对象,意思就是说利用const进行修饰的变量的值在程序的任意位置将不能再被修改。因为常量在定以后就不能被修改,所以定义时必须初始化。
const对象默认为文件的局部变量
在全局作用域里定义非const变量时,它在整个程序中都可以访问。在全局作用域声明的const变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。通过指定const变量为extern,就可以在整个程序中访问const对象。非const变量默认为extern。
const 引用
1 | const int a=1; |
const对象不能绑定到非const 的引用上,反之,const引用可以绑定非const对象。
const对象的动态数组
在自由存储区中创建的数组存储了内置类型的const对象,则必须为这个数组提供初始化: 因为数组元素都是const对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化。同时C++允许定义类类型的const数组,但该类类型必须提供默认构造函数:
1 | //error |
指针和const
指向常量的指针: C++为了保证不允许使用指针改变所指的const值这个特性,强制要求这个指针也必须具备const特性。
1 | const int a = 21; |
允许把非const对象的地址赋给指向const对象的指针
1 | int a = 21; |
常指针(const指针)
1 | int a = 21; |
可以从右往左把上述定义语句读作"指向int型对象的const指针"。与其他const量一样,const指针的值不能被修改,这意味着不能使curErr指向其他对象。Const指针也必须在定义的时候初始化。
指向常量的常指针(指向const对象的const指针)
1 | const int a = 21; |
函数和const
在一个类中,任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性(const成员函数只能调用其他const成员函数)。使用const关键字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。
1 | class A{ |
const是定义为const函数的组成部分,那么就可以通过添加const实现函数重载。
const 修饰函数返回值:它的含义和const修饰普通变量以及指针的含义基本相同
const修饰函数参数:传递过来的参数在函数内不可以改变,如果是指针,则参数指针所指内容为常量不可变,如果参数为引用,为了增加效率同时防止修改
const限定符和static的区别
static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化。const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数
new和delete操作符
在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。如下所示:
1 | int *p = (int*) malloc( sizeof(int) * 10 ); //分配10个int型的内存空间free(p); //释放内存 |
在C++中,这两个函数仍然可以使用,但是C++又新增了两个关键字,new 和 delete:new 用来动态分配内存,delete 用来释放内存。
用 new 和 delete 分配内存更加简单:
1 | int *p = new int; //分配1个int型的内存空间delete p; //释放内存 |
如果希望分配一组连续的数据,可以使用 new[]:
1 | int *p = new int[10]; //分配10个int型的内存空间delete[] p; |
内联函数 inline
在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数
1 | //内联函数,交换两个数的值 |
要在函数定义处添加 inline 关键字,在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。内联函数不应该有声明,应该将函数定义放在本应该出现函数声明的地方。
使用内联函数的缺点也是非常明显的,编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大,所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数。
C语言宏:是预处理命令的一种,它允许用一个标识符来表示一个字符串(常量或者是表达式)
函数的默认参数
定义函数时可以给形参指定一个默认的值,这样调用函数时如果没有给这个形参赋值。同时调用函数时可以省略有默认值的参数。
1 | void func(int n, float b=1.2, char c='@'){ |
默认参数除了使用数值常量指定,也可以使用表达式指定。默认参数只能放在形参列表的最后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。
函数重载
C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)
会被重命名为_Swap_int_int
,void Swap(float x, float y)
会被重命名为_Swap_float_float
。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议。