目录
C++是一门面向对象语言,并且兼容C语言,在C语言的基础上增加了许多新语法。
一.C++关键字
C++的关键字有63个,比C还多了31个。
二.命名空间
C++中,标准库中有大量的函数,变量,类,标准库存在于全局作用域,难免会出现命名冲突。这时就需要命名空间来将标识符的名称进行本地化,避免出现命名冲突或名字污染。
注意:定义在局部中的变量/函数/类可与全局域中的命名相同!(编译器优先查找局部域)
//1
#include<iostream>
using namespace std;
int rand = 0;//定义在全局作用域,与标准库中的rand()函数命名冲突
int main()
{
return 0;
}
//2
#include<iostream>
using namespace std;
int main()
{
int rand = 0;//不会报错,定义在局部作用域中,不会与全局作用域的rand()发生命名冲突
return 0;
}
1.定义
定义命名空间需要使用namespace关键字,后面是命名空间的名字,然后像函数一样加上{}。
代码演示:
namespace NS
{
// 命名空间中可以定义变量/函数/类型
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
//命名空间也可以嵌套
namespace NS1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace NS2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
注意:同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中!
2.使用
有三种方式访问命名空间内的变量/函数/类。
直接展开命名空间:
#include<iostream>
using std::cout;
namespace NS
{
int a = 10;
}
using namespace NS;//直接展开命名空间,可理解为命名空间“失效”了
int main()
{
cout << a;
return 0;
}
直接展开命名空间的成员:
#include<iostream>
using std::cout;
namespace NS
{
int a = 10;
}
using NS::a;//直接展开一个变量/函数//类
int main()
{
cout << a;
return 0;
}
直接访问变命名空间的成员:
#include<iostream>
using std::cout;
namespace NS
{
int a = 10;
}
int main()
{
cout << NS::a;//指定访问NS命名空间的a变量
return 0;
}
三. C++输入输出语句
C++的输入输出语句分别是cin和cout配合>>(流提取)<<(流插入)使用,属于iostream头文件中(早期的编译器有#include<iostream.h>的用法),能自动识别输入输出数据的类型,原理是利用了函数重载。
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
// 可以自动识别变量的类型
cin>>a;
cin>>b>>c;
cout<<a<<endl;
cout<<b<<" "<<c<<endl;
return 0;
}
四.缺省参数
缺省参数是用于函数参数的,为声明或定义函数时的参数指定一个缺省值,调用该函数时,如果没有传入实参该参数就会使用缺省值。
#include<iostream>
using namespace std;
int add(int a = 0, int b = 1)
{
return a + b;
}
int main()
{
cout << add(10, 11) << endl;//正常传参,输出结果是10+11
cout << add() << endl;//不传参,函数调用后参数使用的是缺省值,结果是0+1
return 0;
}
1.缺省类型
全缺省:
#include<iostream>
using namespace std;
int add(int a = 0, int b = 1, int c = 2)//全缺省,所有参数都给上缺省值
{
return a + b + c;
}
int main()
{
cout << add(10, 11, 12) << endl;//正常传参,输出结果是10+11+12
cout << add() << endl;//不传参,函数调用后参数使用的是缺省值,结果是0+1+2
return 0;
}
半缺省 :
#include<iostream>
using namespace std;
int add(int a, int b = 1, int c = 2)//半缺省,部分参数都给上缺省值,从右往左给不能间隔,不能只给ac不给b缺省值
{
return a + b + c;
}
int main()
{
cout << add(10, 11, 12) << endl;//正常传参,输出结果是10+11+12
cout << add(10) << endl;//传一个参,函数调用后没有传实参的参数使用的是缺省值,结果是0+1+2
cout << add(10, 11) << endl;//传两个参,函数调用后没有传实参的参数使用的是缺省值,结果是10+11+2
return 0;
}
注意:
1.半缺省的缺省参数必须从右往左给,中间不能有间隔
2.函数的声明和定义不能同时出现缺省参数,一般是声明给定义不给
3.缺省值必须是常量或者全局变量
五.函数重载
C语言不允许同名函数存在,而C++允许,原因是函数重载,函数重载的条件是:同一个命名空间里,函数名相同,参数不同(个数,类型,顺序)。
1.函数重载类型
参数个数不同:
#include<iostream>
using namespace std;
int add(int a, int b)//两个参数
{
return a + b;
}
int add(int a, int b, int c)//三个参数
{
return a + b + c;
}
int main()
{
cout << add(1, 2) << endl;//两个参数就会调用只有两个参数的add函数
cout << add(1, 2, 3) << endl;//三个参数就会调用只有三个参数的add函数
return 0;
}
参数类型不同:
#include<iostream>
using namespace std;
int add(int a, int b)//两个int参数的add函数
{
return a + b;
}
double add(double a, double b)//两个double参数的add函数
{
return a + b;
}
int main()
{
cout << add(1, 2) << endl;//两个int参数就会调用只有两个int参数的add函数
cout << add(1.1, 2.2) << endl;//两个double参数就会调用只有两个double参数的add函数
return 0;
}
参数顺序不同:
#include<iostream>
using namespace std;
double add(double a, int b)//第一个参数是double,第二个参数是int
{
return a + b;
}
double add(int a, double b)//第一个参数是int,第二个参数是double
{
return a + b;
}
int main()
{
cout << add(1.1, 2) << endl;//调用第一个add函数
cout << add(1, 2.2) << endl;//调用第二个add函数
return 0;
}
还有一种需要注意的情况,当函数重载和缺省参数结合时:
#include<iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
int add(int a, int b, int c = 1)
{
return a + b + c;
}
//上面两个函数构成函数重载,因为参数个数不同
int main()
{
//下面调用不明确,编译器会报错,只传两个参数编译器不清楚应该调用哪个add函数
cout << add(10, 11) << endl;
cout << add(1, 2) << endl;
return 0;
}
注意:
1.函数重载需要在同一个命名空间内才构成
2.只有返回值不同不能构成函数重载
2.C++函数重载的原理
本质是C++的函数名修饰规则和C不一样。
以下是编译的大概过程:程序运行时需要经历预处理,编译,汇编,链接
实际项目通常是由多个头文件和多个源文件构成,当前a.cpp 中调用了 b.cpp 中定义的 Add 函数时,编译后链接前, a.o 的目标文件中没有Add 的函数地址,因为 Add 是在 b.cpp 中定义的,所以 Add 的地址在 b.o 中。就需要符号表来查找对应的函数地址来调用函数。
以下是Linux系统下使用gcc编译器C语言的函数放入符号表后的函数名:
以下是Linux系统下使用gcc编译器C++的函数放入符号表后的函数名:
VS编译器的函数名修饰比较复杂。
编译的过程中,每个文件(.c .h .cpp等)会生成一个符号表,里面存放着该文件里的变量/函数/类的名字,以及他们对应的地址,然后会把所有文件的符号表合并在一起。
C语言中函数的名字存入符号表中就是"_函数名",所以相同函数名的函数就会发生冲突,因为名字相同但是会有多个地址。
而C++的函数有自己的函数名修饰规则,函数名相同的情况下,参数个数,类型,顺序不同其放入符号表的名字也不同,所以可以实现函数重载。
注意:函数存入符号表中可以根据返回值的不同存入不同的修饰后的函数名(此时函数参数完全一样),但是实际调用的时候不能区分调用的是哪一个函数!
六. 引用
1.引用概念和特性
引用就是引用对象的别名,对于别名的修改也会影响到引用对象本身
#include<iostream>
using namespace std;
int main()
{
int a = 0;
int& b = a;//创建b引用a,以后b就是a的别名
b++;//b++就相当于a++
cout << a << endl;//输出结果为1
return 0;
}
注意:
1. 引用在定义时必须初始化2. 一个变量可以有多个引用3. 引用一旦引用一个实体,就不能再引用其他实体
2.常引用
#include<iostream>
using namespace std;
int main()
{
const int a = 0;
int& b = a;//因为a是常量,如果b能够引用就可以修改a了,所以这句是错的
const int& b = a;//对于常量的引用需要用常引用
int& b = 10;//同理,10为常量,这句是错的
const int& b = 10;//所以常引用才能引用常量
int c = 1;
double& d = c;//这句是错的,原因是int和double类型不同,会出现隐式类型转换,有一个临时变量,类型是double,值是变量c的值,临时变量具有常性,d引用的是这个临时变量
const double& d = c;//使用常引用就没问题了
return 0;
}
注意:涉及到类型不同就会出现隐式类型转换,会产生一个临时变量,临时变量具有常性!在引用中,权限可以缩小平移,但不能放大!
3.使用场景
做参数:
#include<iostream>
using namespace std;
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
swap(a, b);//有了引用就不需要传地址了,比较方便
return 0;
}
做返回值:
//传值返回
#include<iostream>
using namespace std;
int func()
{
int n = 0;
return n;
}
int main()
{
int ret = func();//传值返回实际上会创建一个临时变量保存func()的返回值,然后再传给ret,因为func函数的栈帧会销毁,假如ret前加上&表示引用就必须是常引用,因为传值返回的临时变量具有常性,并且临时变量的生命周期延长,和ret共存亡
return 0;
}
//传引用返回(引用的对象出函数后不存在的后果)
#include<iostream>
using namespace std;
int& func()
{
int n = 0;
return n;
}
int main()
{
int& ret = func();//实际上func()栈帧已经被销毁,但引用的值n(引用返回不会创建临时变量)还在原来的栈帧里面,访问ret就是非法的
cout << ret << endl;//输出的是0,第一次没问题是因为cout的<<需要传参,先访问传参再开辟栈帧覆盖ret所指向的位置
cout << ret << endl;//输出的是很大的值,上一次cout开辟栈帧就把ret所引用的地方覆盖了
return 0;
}
当然,如果ret所引用的位置比较远,即使cout的<<开辟栈帧也不会覆盖到。
#include<iostream>
using namespace std;
int& func()
{
int arr[1000];//创建很大的数组让ret的位置距离main函数的栈帧比较远
int n = 0;
return n;
}
int main()
{
int& ret = func();
cout << ret << endl;//输出的是0
cout << ret << endl;//输出的是0
return 0;
}
下面程序的输出是?
#include<iostream>
using namespace std;
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;//输出是7,第二次调用Add()覆盖了原本ret的值
return 0;
}
注意:如果引用的对象出函数后不存在或者销毁了就不能使用引用返回,否则就会出现上面的情况!(不同编译器结果不同)
4.传值传引用的效率差距
下面是传参数用引用的效率差距:
#include<iostream>
#include <time.h>
using namespace std;
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
下面是返回值传引用的效率差距:
#include<iostream>
#include <time.h>
using namespace std;
#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
// 计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
TestReturnByRefOrValue();
return 0;
}
可以看到效率差距是比较明显的。
5.引用和指针的区别
引用就是引用对象的一个别名,不占空间,和引用对象共用一块空间,这是语法层面上的,下面看看底层怎么实现的。
#include<iostream>
using namespace std;
int main()
{
int a = 10;//创建a
int& ra = a;//用引用的方式把a改成20
ra = 20;
int* pa = &a;//用指针的方式把a改成20
*pa = 20;
return 0;
}
从汇编的角度来看,实际上引用和指针实现修改变量a的值的方式是一样的。
引用和指针的不同点:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。2. 引用在定义时必须初始化,指针没有要求3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体4. 没有NULL引用,但有NULL指针5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小7. 有多级指针,但是没有多级引用8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理9. 引用比指针使用起来相对更安全
传引用传参(任何情况都可以用)
1.提高效率
2.做输出型参数
传引用返回(对象出了作用域还在)
1.提高效率
2.修改返回对象
七.内联函数
函数调用的时候会开辟栈帧,如果是比较简单的函数大量调用就会浪费许多不必要的时间。
而内联函数可以让函数原地展开,不用开辟栈帧,这是一种以空间换时间的做法。
#include<iostream>
using namespace std;
inline int add(int a, int b)//在函数前面加上inline关键字就是内联函数了
{
return a + b;
}
int main()
{
int res = add(1, 2);
cout << res << endl;
return 0;
}
以下是加上inline关键字的函数汇编指令:
可以看到是直接原地展开函数进行计算。
以下是没有加上inline关键字的函数汇编指令:
可以看到第3句有call指令进去后就会开辟add函数的栈帧然后再进行计算,后面是add函数的地址(VS会将函数的地址封装两层实际上call后面的地址不是add函数的真实地址)
内联函数的优缺点和注意事项:
优点:不用开辟函数栈帧,减少性能开销
缺点:可能会使文件变大
注意:inline对于编译器是一个建议,如果函数内的语句多于一定程度,编译器会无视建议开辟函数栈帧然后执行函数内容,或者函数内有递归也会无视。
也不建议内联函数声明和定义分离,因为原地展开函数就意味着没有函数地址也就不会进符号表,会导致链接错误!
八.auto关键字
auto是C++11引入的关键字,能够自动推导类型,对于类型很长的变量来说比较好用。
#include<iostream>
using namespace std;
int main()
{
int a = 0;
auto b = a;//auto自动推导,b就是int类型
auto c = &a;//c就是int*类型
auto& d = a;//d就是a的引用
return 0;
}
九.范围for
范围for可以自动遍历数组中的所有元素,比较方便。
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 1,2,3,4,5 };
//auto自动推导类型,把值传给x变量,范围for会遍历数组中的所有元素
for (auto x : arr)
{
cout << x << " ";
}
cout << endl;
//想要修改数组内容就加上引用
for (auto& x : arr)
{
x *= 2;
cout << x << " ";
}
return 0;
}
十. 指针空值
C语言的空指针是NULL,但在C++中被定义成了数字0,而不是((void*)0)。C++中的空指针是nullptr。
//这里说明NULL是一个宏,这里是条件编译,C++的NULL是0,C语言的NULL是((void *)0)
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字入的。2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
以上就是C++的基础语法了,如有问题可在评论区提出。