目录
什么是C++?
◇ C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大 的 程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年 代, 计算机 界提出了OOP(object oriented programming:面向对象)思想,支持面向对 象的程序设计语言 应运而生。
◇ 1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了 一 种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是 基于C语言而 产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据 类型为特点的基于对象的 程序设计,还可以进行面向对象的程序设计。
1979年,贝尔实验室的本贾尼等人试图分析unix内核的时候,试图将内核模块化,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为C with classes。
命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{
printf("%d\n", rand);
return 0;
}
// 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”
命名空间定义
这段程序的结果是1,而不是0.
- 在C语言的学习中我们知道,当局部变量和全局变量重名的时候,优先使用局部变量
- 所以这里的打印的结果是局部变量a的值,而不是全局变量a的值
再看一段程序
△ 这里我们包含了一个头文件stdlib.h,这个头文件中有一个函数就是rand(),就像编译器警 告中所描述的那样
△ 还创建了一个全局变量rand,该全局变量的名字和函数rand的名字是相同的
△ 我们知道,在预处理阶段,stdlib.h中的内容会全部复制到main所在的源文件中,此时在 该源文件中就出现了俩个rand符号,而且表示函数的rand出现在全局变量rand之前,所以 在制作符号表的时候就无法正常进行,编译器就会报错。
△ 按照我们C语言中所学习的,自己定义的符号名是不能重名的,而且也不能和关键字相 同,遇到这种情况我们只能给全局变量换一个名字
但是在C++中,像上面这种符号名重复的情况是可以的,这个时候就需要使用到C++中的域,这里是指命名空间
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。
namespace LS
{
int rand = 10;
}
int main()
{
printf("%d\n", LS::rand);
return 0;
}
这样便可成功打出全局变量 rand
再看一段代码,附上讲解注释
//命名空间,不影响变量生命周期,只是限定域,放在静态区里
//编译器查找规则:先找局部,再全局
namespace bit//命名空间的名字
{
int rand = 10;//不会建立栈帧,是全局变量
int x = 0;//不会在命名空间里面找
}
void func()
{
printf("%d\n", rand);//1998654880--函数地址
printf("%d\n", bit::x);//用::可以在域里面查找
printf("%d\n", bit::rand);
}
int main()
{
func();
printf("%d\n", rand);//1998654880--地址
return 0;
}
- namespace 后面跟的是命名空间的名字,中间必须用空格隔开
- 在使用命名空间中的变量名时,需要用到俩个冒号(:: ),叫做域作用限制符,要使用到命名空间中的哪个变量,域作用限制符::后面就跟哪个变量的名字
♢ 通俗一点的理解就是,命名空间相当于给我们创建的变量建了一个围墙,别人是看不到围 墙里的东西的,只有通过域作用限制符(::)将围墙里的东西告诉外面的人,别人才能知道里 面有这么一个东西。
◇ 命名空间的作用就是改变编译器的查找符号名的顺序
◇ 没有命名空间时,编译器查找符号名是先查找局部,局部没有再查找全局
◇ 使用了命名空间后,如果不使用域作用限制符,这个命名空间还是相当于没有,编译器查 找符号名的顺序和以前一样
◇ 当使用域作用限制符时,编译器就不采用之前的查找方式,而是直接中命名空间中查找:: 后面的符号名
我们在C语言中是尽量避免使用全局变量的,因为全局变量很有可能在某个函数中使用到,从而造成全局变量的修改,而C++中使用了命名空间就很好了避免了这个问题。
//命名空间可以嵌套命名空间 namespace N1 { int a; int b; int Add(int left, int right) { return left + right; } namespace N2 { int c; int d; int Sub(int left, int right) { return left - right; } } } int main() { //嵌套 N1::a = 1; N1::N2::d = 2; }
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中:
在命名空间中,不仅能够定义变量,而且可以定义函数:
namespace bit
{
int rand = 10;
int x = 1;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
命名空间的使用有三种方式:
★ 加命名空间名称及作用域限定符
int main()
{
printf("%d\n", N::a);
return 0;
}
★ 使用using将命名空间中某个成员引入
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
只有加上这句话,在后面的程序中就可以直接使用命名空间中的该变量,相当于将该变量放在了围墙外面。
★ 使用using namespace 命名空间名称 引入
typedef struct student
{
char name[20];
int age;
int grade;
};
namespace Stu
{
struct student s1 = { "xiaoyan",20,99 };
struct student s2 = { "tangsan",19,20 };
}
using namespace Stu;
int main()
{
printf("%s同学,%d岁,%d分\n",s1.name,s1.age,s1.grade);
printf("%s同学,%d岁,%d分\n", s2.name, s2.age, s2.grade);
}
此时,命名空间中的所有变量都可以在下面的程序中直接使用了,都不用再写命名空间的名字了,也不用挨个将命名空间中的变量释放出来。
注意:
- 这里用using将命名空间中某个成员引入和使用using namespace 命名空间名称引入在我们练习或者是比较短的程序中可以使用
- 在写一个大的项目的时候,最好不要这样写,还是要老老实实的使用域作用限制符来使用命名空间中的变量。
在C++程序中,我们通常在程序的最前面会看到 using namespace std 这样一个语句,这个语句的意思是什么呢?很多小伙伴都认为它是C++程序中必须有的,记住就行了,只要写C++程序就需要写这么一句话,这是错误的。
在上面我们也写了函数,而且是放在命名空间中的,C++中也有很多的库函数,像cout,cin等等,就类似于C语言中的print和scanf这样的库函数,官方提供的库函数都写在了一个名字为std的命名空间中。
- using namespace std的意思就是将官方库函数所在命名空间的墙拆掉,在程序中直接像C语言一样使用库函数就行,不用再通过域作用限制符来调用。
C++输入&输出
输出
- 使用using namespace std
#include <iostream> using namespace std; int main() { cout << "hello world" << endl; return 0; }
- 不使用using namespace std
int main() { std::cout << "hello world" << std::endl; return 0; }
它们的结果都是一样,都是打印出了hello wrold,区别就在于:
- 使用了using namespace std后,直接调用cout函数就可以
- 没有使用using namespace std,需要用标准形式(命名空间名 :: 变量名)来调用cout函数
我们再来看一段代码
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 20;
cout << a + b << endl;
return 0;
}
输出为:30
- cout输出中,它能自动识别变量的类型,如果使用C语言中的printf的话,就需要以%d的形式输出,而cout只需要直接写变量名就行。
- 但是当输出的数是格式化的数据时,cout来控制格式就不方便了,需要使用C中的printf来控制输出的格式
例如:
int main()
{
double pi = 3.1415926f;
cout << pi << endl;
printf("%.2lf\n", pi);
return 0;
}
输出:3.14159
输出:3.14
使用C中的printf很容易的就将输出控制在了小数点后俩位,而cout则无法控制。
所以说,到底使用cout还是printf需要我们自己来决定,哪个方便用哪个,因为C++是完全兼 容C语言的。
输入
int main()
{
int a = 0;
int b = 0;
cin >> a >> b;
int c = a + b;
cout << c << endl;
return 0;
}
输入:10 20
输出:30
cin和C语言中的scanf是一样的,只它也不能进格式化控制。
这里有几点说明:
1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及 按命名空间使用方法使用std。
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包 含 <iostream >头文件中。
3. <<是流插入运算符,>>是流提取运算符。这里可以形象的理解,cout<<a,就是变量a流到了 cout控制台,cin>>a,就是从cin控制台流到了a中。
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输 入输出可以自动识别变量类型。
5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识。这些 在后续的博客中会写到
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应 头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间, 规定C++头文件不带.h;旧编译器(vc 6.0)中还支持格式,后续编译器已不支持,因 此推荐使用+std的方式。
补充:
std命名空间的使用惯例:
std是C++标准库的命名空间,如何展开std使用更合理呢? 1. 在日常练习中,建议直接using namespace std即可,这样就很方便。 2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对 象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模 大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。
缺省参数
缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实 参则采用该形参的缺省值,否则使用指定的实参。
看一段代码
这里,Func函数中 int a = 0就是缺省参数
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func();
Func(10);
return 0;
}
这里将打印a的值封装在一个函数func中,在main函数中将实参a的值传给了形参a,从而打印出a的值。
如果这里不给形参会发生什么?
我们知道,在C语言中,必须有实参传过去才行,否则就会报错,但是在C++中就可以不传实参。
输出:0
10
缺省参数分类
全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Func();
Func(1);//a
Func(1, 2);
Func(1, 2, 3);
return 0;
}
半缺省函数
//半缺省/部分缺省---只能从右往左连续缺省
void Func(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Func(1);//至少得传一个
Func(1, 2, 3);
return 0;
}
注意:
1. 半缺省参数只能从右往左给,中间不能间隔,缺省参数和缺省参数必须挨着。
2. 调用函数时传入的实参,只能从左向右传。
3. 不是缺省参数的形参必须有实参传入,缺省参数可传可不传
在使用缺省参数时,还有几个需要注意的事项,这是针对所有含有缺省参数的函数的。
1.缺省参数不能在函数的定义和声明中同时出现,否则编译器会凌乱,当函数既有声明又有定义的 时候,将缺省参数写在函数的声明中。
2.函数声明中的缺省参数和函数定义中的缺省参数是不同的,当调用函数时没有传入实参,那么此时编译器该听谁的?听函数声明的还是听函数定义的?答案是听函数声明的。
3.缺省值必须是常量或者全局变量
缺省参数的应用
这里我们在调用初始化函数的时候是不知道需要开辟多少空间的,所以就没有传个数的参数,但是在函数的定义中使用了缺省参数,这个缺省参数也就是开辟空间的默认值,默认是10个,此时就不用纠结到底开辟多少个了。
当我们清楚的知道需要开辟多少个空间的时候也可以直接将实参传过去。
函数重载
函数重载的概念
♟ 自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重 载了。
♟ 比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个 是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这 些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题。
函数重载也是这样,即一个函数名对应着多个函数,如:
#include <iostream>
void Print(int a)
{
cout <<"整型数据:"<< a << endl;
}
void Print(float b)
{
cout << "浮点型数据:"<< b << endl;
}
int main()
{
int a = 10;
float b = 3.14f;
Print(a);
Print(b);
return 0;
}
输出:整型数据:10
浮点型数据:3.14
上面代码中,定义了两个函数,一个是打印整型数据的,一个是打印浮点型数据的,但是它们的函数名是相同的,在main函数中,只要在调用时将相应的实参传入就会调用相应的函数。
这种情况在C语言中是绝对不被允许的,函数名是不可以重名的,只能打印整数的函数用一个函数名,打印浮点数的函数用另一个函数名。
这就是C++中的函数重载。
函数重载的类型
1、参数类型不同
//1、类型不同
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void swap(double* a, double* b)
{
double tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
cout << add(1, 2) << endl;
cout << add(1.1, 2.2) << endl;
int a = 0, b = 1;
swap(&a, &b);
double c = 5.2, d = 6.6;
swap(&c, &d);
//自动识别类型,本质是函数重载支持
//ostream& operator<<(int val);
//ostream& operator<<(doouble val);
cout << a <<" "<< b << endl;
cout << c <<" "<< d << endl;
return 0;
}
2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(10);
}
3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
void f(int a, int b)
{
cout << "f(int a,char b)" << endl;
}
void f()
{
cout << "f()" << endl;
}
int main()
{
f();
f(10, 'A');
f('A', 10);
return 0;
}
构成函数重载---f()调用会报错,存在歧义
void f()
{
cout << "f()" << endl;
}
void f(int a, int b)
{
cout << "f(int a,char b)" << endl;
}
int main()
{
f(10);//歧义 二义性
f(10, 'A');
f();
return 0;
}
在调用的时候,不传任何实参,可以调用上面的那个没有形参的f,也可以调用下面的带有缺省参数的f,所以会报错,报对重载函数调用不明确的错误。
这个现象称为调用时的二义性。
通过上面俩段程序可以看出,带有缺省参数的函数是可以重载的,但是在调用的时候要明确调用哪个,像上面的例子第一个函数就无法调用,因为无法在调用时明确的调用。
总之,只要在调用的时候不产生二义性,带有缺省参数的函数也是可以重载的。
还记得前面在讲解输入输出的时候,cout函数和cin函数可以自动识别类型吗?到这里你应该明白了,它之所以能够自动识别变量的类型,也是因为函数重载,具体的实现在以后的学习中会给大家讲解。
面试题
返回值不同,能否构成重载
--返回值不同,不构成重载原因,并不是函数名修饰
真正原因是调用时的二义性,无法区分,调用时不指定返回类型
本篇结束