C与C++头文件的区别和联系
1、旧的 C++ 头文件,如 iostream.h、fstream.h 等将会继续被支持,尽管它们不在官方标准中,这些头文件的内容不在命名空间 std 中。新的 C++ 头文件,如 iostream、fstream 等包含的基本功能和对应的旧版头文件相似,但头文件的内容在命名空间 std 中。
2、标准C头文件如 stdio.h、stdlib.h 等继续被支持,头文件的内容不在 std 中。具有C库功能的新C++头文件具有如 cstdio、cstdlib 这样的名字,它们提供的内容和相应的旧的C头文件相同,只是内容在 std 中。
命名空间声明使用的注意事项
#include <iostream> using namespace std; //声明命名空间std int main(){ cout<<"......."<<endl; return 0; }
将std直接声明在所有函数外部,这样虽然使用方便,但在中大型项目开发中是不被推荐的(小型项目或者测试学习声明在全局变量中还是很方便的),这样做增加了命名冲突的风险,推荐在函数内部声明std。
#include <iostream> void func(){ using namespace std; //必须重新声明 cout<<"http://c.biancheng.net"<<endl; } int main(){ using namespace std; //声明命名空间std cout<<"。。。。。。"<<endl; func(); return 0; }
New与delete
和 malloc() 一样,new 也是在堆区分配内存,必须手动释放,否则只能等到程序运行结束由操作系统回收。为了避免内存泄露,通常 new 和 delete、new[] 和 delete[] 操作符应该成对出现,并且不要和C语言中 malloc()、free() 一起混用。
例如:
int *p = new int; //分配1个int型的内存空间 delete p; //释放内存 ----------------------------------------或---------------------------------------- int *p = new int[10]; //分配10个int型的内存空间 delete[] p;
函数的默认参数
#include<iostream> using namespace std; //带默认参数的函数 void func(int n, float b=1.2, char c='@'){ cout<<n<<", "<<b<<", "<<c<<endl; } int main(){ //为所有参数传值 func(10, 3.5, '#'); //为n、b传值,相当于调用func(20, 9.8, '@') func(20, 9.8); //只为n传值,相当于调用func(30, 1.2, '@') func(30); return 0; }
C++规定,默认参数只能放在形参列表的最后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。实参和形参的传值是从左到右依次匹配的,默认参数的连续性是保证正确传参的前提。
函数重载
函数的重载的规则:
- 函数名称必须相同。
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
- 函数的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为函数的重载。
//交换 int 变量的值 void Swap(int *a, int *b){ int temp = *a; *a = *b; *b = temp; } //交换 float 变量的值 void Swap(float *a, float *b){ float temp = *a; *a = *b; *b = temp; } //交换 char 变量的值 void Swap(char *a, char *b){ char temp = *a; *a = *b; *b = temp; } //交换 bool 变量的值 void Swap(bool *a, bool *b){ char temp = *a; *a = *b; *b = temp; }
C++是如何做到函数重载的:
C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_int,void Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。(不同的编译器有不同的重命名方式,这里仅仅举例说明,实际情况可能并非如此)。从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。
函数模板
值(Value)和类型(Type)是数据的两个主要特征,它们在C++中都可以被参数化。
所谓函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。
#include <iostream> using namespace std; template<typename T> void Swap(T *a, T *b){ T temp = *a; *a = *b; *b = temp; } int main(){ //交换 int 变量的值 int n1 = 100, n2 = 200; Swap(&n1, &n2); cout<<n1<<", "<<n2<<endl; //交换 float 变量的值 float f1 = 12.5, f2 = 56.93; Swap(&f1, &f2); cout<<f1<<", "<<f2<<endl; return 0; }
template是定义函数模板的关键字,它后面紧跟尖括号<>,尖括号包围的是类型参数(也可以说是虚拟的类型,或者说是类型占位符)。typename是另外一个关键字,用来声明具体的类型参数,这里的类型参数就是T。从整体上看,template<typename T>被称为模板头。
定义模板函数的语法:
template <typename 类型参数1 , typename 类型参数2 , ...> 返回值类型 函数名(形参列表){ //在函数体中可以使用类型参数 }
typename关键字也可以使用class关键字替代,它们没有任何区别。类型参数不能为空,多个类型参数用逗号隔开。
类模板
声明类模板的语法为:
template<typename 类型参数1 , typename 类型参数2 , …> class 类名{ //TODO: };
模板头和类头是一个整体,可以换行,但是中间不能有分号。例如:
template<typename 类型参数1 , typename 类型参数2 , …> //这里不能有分号 class 类名{ //TODO: };
例子:
template<typename T1, typename T2> //这里不能有分号 class Point{ public: Point(T1 x, T2 y): m_x(x), m_y(y){ } public: T1 getX() const; //获取x坐标 void setX(T1 x); //设置x坐标 T2 getY() const; //获取y坐标 void setY(T2 y); //设置y坐标 private: T1 m_x; //x坐标 T2 m_y; //y坐标 };
上面的代码仅仅是类的声明,我们还需要在类外定义成员函数。在类外定义成员函数时仍然需要带上模板头,格式为:
template<typename 类型参数1 , typename 类型参数2 , …> 返回值类型 类名<类型参数1 , 类型参数2, ...>::函数名(形参列表){ //TODO: }
第一行是模板头,第二行是函数头,它们可以合并到一行,不过为了让代码格式更加清晰,一般是将它们分成两行。
template<typename T1, typename T2> //模板头 T1 Point<T1, T2>::getX() const /*函数头*/ { return m_x; } template<typename T1, typename T2> void Point<T1, T2>::setX(T1 x){ m_x = x; }
除了 template 关键字后面要指明类型参数,类名 Point 后面也要带上类型参数,只是不加 typename 关键字了。另外需要注意的是,在类外定义成员函数时,template 后面的类型参数要和类声明时的一致。
强制类型转换
强制类型转换的格式为:
(type_name) expression
type_name为新类型名称,expression为表达式。例如:
(float) a; //将变量 a 转换为 float 类型 (int)(x+y); //把表达式 x+y 的结果转换为 int 整型 (float) 100; //将数值 100(默认为int类型)转换为 float 类型
可以自动进行的类型转换一般风险较低,不会对程序带来严重的后果,例如,int 到 double 没有什么缺点,float 到 int 顶多是数值失真。只能强制进行的类型转换一般风险较高,或者行为匪夷所思,例如,char * 到 int * 就是很奇怪的一种转换,这会导致取得的值也很奇怪,再如,int 到 char * 就是风险极高的一种转换,一般会导致程序崩溃。
使用强制类型转换时,程序员自己要意识到潜在的风险。