C++的起源
C++的起源可以追溯到1979年,当时Bjarne Stroustrup(本贾尼·斯特劳斯特卢普,这个翻译的名字不同的地方可能有差异)在贝尔实验室从事计算机科学和软件⼯程的研究⼯作。面对项目中复杂的软件开 发任务,特别是模拟和操作系统的开发⼯作,他感受到了现有语言(如C语言)在表达能力、可维护性和可扩展性方面的不足。
1983年,Bjarne Stroustrup在C语言的基础上添加了面向对象编程的特性,设计出了C++语言的雏形,此时的C++已经有了类、封装、继承等核心概念,为后来的面向对象编程奠定了基础。这⼀年该语言被正式命名为C++。
在随后的几年中,C++在学术界和⼯业界的应⽤逐渐增多。⼀些大学和研究所开始将C++作为教学和研究的首选语言,而⼀些公司也开始在产品开发中尝试使用C++。这⼀时期,C++的标准库和模板等特性也得到了进⼀步的完善和发展。
C++的标准化⼯作于1989年开始,并成⽴了⼀个ANSI和ISO(InternationalStandards Organization)国际标准化组织的联合标准化委员会。1994年标准化委员会提出了第⼀个标准化草案。在该草案中,委员会在保持斯特劳斯特卢普最初定义的所有特征的同时,还增加了部分新特征。
在完成C++标准化的第⼀个草案后不久,STL(Standard Template Library)是惠普实验室开发的⼀系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室⼯作时所开发出来的。在通过了标准化第⼀个草案之后,联合标准化委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。
1997年11月14日,联合标准化委员会通过了该标准的最终草案。1998年,C++的ANSI/IS0标准被投入使用。
命名空间(namespcae)
在c++后期学习中,变量,函数,类都是大量存在的,这些变量、函数和类的名称将都存在于全 局作⽤域中,可能会导致很多冲突。使⽤命名空间的目的是对标识符的名称进⾏本地化,以避免命名 冲突或名字污染,namespace关键字的出现就是针对这种问题的。
在C语言项目中存在命名冲突是一个普遍的问题,如:
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
此时编译器会产生报错, // 编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”
此时我们就可以选择使用namespace
注意:命名空间中可以定义变量/函数/类型
#include <stdio.h>
#include <stdlib.h>
namespace hyx
{
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
printf("%p\n", rand);
printf("%d\n", hyx::rand);
return 0;
}
但是在这里要注意,C++中默认先访问的是全局域,这里就要使用::(域作用限定符),才能保证访问的是namespace中的域。
namespace使用要点:
1.namespace本质是定义出⼀个域,这个域跟全局域各自独立,不同的域可以定义同名变量,所以下面的rand不在冲突了,
2.项目工程中多文件中定义的同名namespace会认为是⼀个namespace,不会冲突。
3.C++标准库都放在⼀个叫std(standard)的命名空间中。
所以在使用C++中的库函数的使用需加上域作用限定符,命名空间为std。
4.项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。
5.命名空间只能定义在全局,但是它还可以嵌套定义
namespace hyx
{
namespace 666
{
int rand = 1;
int Add(int left, int right)
{
return left + right;
}
}
namespace 888
{
int rand = 2;
int Add(int left, int right)
{
return (left + right)*10;
}
}
}
在使用的时候只需多加上一个域作用限定符。
printf("%d\n", hyx::666::rand);
命名空间使用
在项目中推荐使用指定命名空间访问。但是在平常的小练习当中可以使用using,它能将命名空间中的某个成员展开,使其在使用的时候无需加上域作用限定符。
#include<stdio.h>
namespace hyx
{
int a = 0;
int b = 1;
}
// using将命名空间中某个成员展开
using hyx::b;
int main()
{
printf("%d\n", hyx::a);
printf("%d\n", b);
return 0;
}
此时在使用namespace是就无需使用 :: 。
C++输入和输出
<iostream>是Input Output Stream的缩写,是标准的输⼊,输出流库,定义了标准的输入、输 出对象。
std::cin是istream类的对象,它主要面向窄字符的标准输入流。
std::cout是ostream类的对象,它主要面向窄字符的标准输出流。
std::endl是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区
<<是流插入运算符,>>是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)
在使用C++输入和输出会更加方便,count和cin在使用的时候无需手动指定格式,C++输入和输出会自动识别变量类型
⼀般日常练习中我们可以using namespace std,实际项⽬开发中不建议using namespace std。
这里我们没有包含,也可以使用printf和scanf,在包含间接包含了。vs系列编译器是这样的,其他编译器可能会报错。
#include <iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
// 可以⾃动识别变量的类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
cin就相当与scanf,count相当于printf,endl相当与\n。
输入和输出在一个区间内只能输入一个变量,如果有多的变量时需要多加入一对<<或>>。
缺省函数
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参 则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地⽅把 缺省参数也叫默认参数)
全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左 依次连续缺省,不能间隔跳跃给缺省值。
带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
#include <iostream>
#include <assert.h>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func();
Func(10);
return 0;
}
在第一种情况,缺省函数会使用默认值,,第二种情况会使用指定的实参。
函数重载
C++支持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态行为,使用更灵活。C语言是不支持同⼀作用域中出现同名函数的。
1.参数类型不同
#include<iostream>
using namespace std;
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
}
2.参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
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 f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
在调用的时候,会报错,存在歧义,编译器不知道调用谁。
引用
引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器为不会引用变量开辟内存空间, 它和它引用的变量共用同⼀块内存空间。
类型& 引用别名 = 引用对象;
引用的特性
引用在定义时必须初始化
⼀个变量可以有多个引用
引用⼀旦引用⼀个实体,再不能引用其他实体
引用的使用
引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被 引用对象。
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
指针变量也可以取别名,这样就不需要用到二级指针,相对而言简化了程序
const引用
可以引用⼀个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访 问权限在引用过程中可以缩小,但是不能放大
int main()
{
const int a = 10;
//int& ra = a;
const int& ra = a;
int b = 20;
const int& rb = b;
return 0;
}
如果这里的常量先前就用const限定,在引用的时候就必须加上const。
如果这个常量没用使用const限定,在引用的时候也可以加上const,但这会导致引用的权限缩小,只能读不能写。
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& ra = 30;
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// int& rd = d;
const int& rd = d;
return 0;
}
如果想引用表达式的时候,也要加上const,因为编译器在处理表达式的时候会创建一个临时对象,(所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。)要想引用临时对象,就要加上const,代码中的两个例子在编译的时候便会报错
// 编译报错: “初始化”: 无法从“int”转换为“int &”
// 编译报错:“初始化”: 无法从“double”转换为“int &”
inline函数
用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。
内联函数与宏函数及其相似,不过宏函数在实现的过程中比较复杂且容易出错,不方便调试。
例如实现加法的宏
// 实现⼀个ADD宏函数的常⻅问题
//#define ADD(int a, int b) return a + b;
//#define ADD(a, b) a + b;
//#define ADD(a, b) (a + b)
// 正确的宏实现
#define ADD(a, b) ((a) + (b))
使用inline也可以形成类似的结果
inline int Add(int x, int y)
{
int ret = x + y;
return ret;
}
只要加上inline在编译的时候函数在调用的地方就会形成向宏一样的展开,可以通过汇编观察程序是否展开,有call Add语句就是没有展开,没有就是展开了。
inline对于编译器而言只是⼀个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展 开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁 调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
nullptr
NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。不论采取何种 定义,在使用空值的指针时,都不可避免的会遇到⼀些麻烦,本想通过f(NULL)调用指针版本的 f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。f((void*)NULL); 调用会报错。
C++11中引入nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换 成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,而不能被转换为整数类型。