C++基础知识
目录
一、命名空间
1.1 namespace的价值
1.2 namespace的定义
1.3 namespace的使用
二、C++输入和输出
三、函数的缺省参数
四、函数重载
五、引用
5.1 引用的概念和定义
5.2 引用的特性
5.3 引用的使用
5.4 const引用
5.5 指针和引用的关系
一、命名空间
1.1 namespace的价值
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作⽤域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
c语⾔项目类似下面程序这样的命名冲突是普遍存在的问题,C++引⼊namespace就是为了更好的解决这样的问题
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{// 编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”printf("%d\n", rand);return 0;
}
这段程序会报错,因为当我们包含头文件时,stdlib中有rand函数,就会发生重定义。
那我们要如何解决这个问题呢?
可以使用namespace来解决这个问题。
1.2 namespace的定义
- 定义命名空间,需要使⽤到namespace关键字,后面跟命名空间的名字,然后接⼀对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
- namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量,所以下面的rand不在冲突了。
- C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
-
namespace只能定义在全局,当然他还可以嵌套定义。
-
项⽬⼯程中多文件中定义的同名namespace会认为是⼀个namespace,不会冲突。
-
C++标准库都放在⼀个叫std(standard)的命名空间中。
#include <stdio.h>
#include <stdlib.h>
// 1. 正常的命名空间定义
// zx是命名空间的名字,⼀般开发中是⽤项⽬名字做命名空间名。
namespace zx
{// 命名空间中可以定义变量/函数/类型int rand = 10;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}
int main()
{// 这⾥默认是访问的是全局的rand函数指针printf("%p\n", rand);// 这⾥指定zx命名空间中的randprintf("%d\n", zx::rand);return 0;
}
命名空间还可以嵌套,如下所示:
//2. 命名空间可以嵌套
namespace ZX
{namespace A{int rand = 1;int Add(int left, int right){return left + right;}}namespace B{int rand = 2;int Add(int left, int right){return (left + right)*10;}}
}
int main()
{printf("%d\n", ZX::A::rand);printf("%d\n", ZX::B::rand);printf("%d\n", ZX::A::Add(1, 2));printf("%d\n", ZX::B::Add(1, 2));return 0;
}
1.3 namespace的使用
编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。所以下面程序会编译报错。所以我们要使用命名空间中定义的变量/函数,有三种方式:
- 指定命名空间访问,项目中推荐这种方式。
- using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种方式。
- 展开命名空间中全部成员,项目不推荐,冲突风险很⼤,日常小练习程序为了方便推荐使用。
#include<stdio.h>
namespace zx
{int a = 0;int b = 1;
}
int main()
{// 编译报错:error C2065: “a”: 未声明的标识符printf("%d\n", a);return 0;
}
指定命名空间访问:
#include<stdio.h>
namespace N
{int a=10;
}
int main()
{printf("%d\n", N::a);return 0;
}
使用using展开命名空间
#include<stdio.h>
namespace N
{int a=10;int b=20;
}// 展开命名空间中全部成员
using namespce N;
int main()
{printf("%d\n", a);printf("%d\n", b);return 0;
}
使用using展开命名空间的某一个成员
#include<stdio.h>
namespace N
{int a=10;int b=20;
}// 展开命名空间中部分成员
using N::b;
int main()
{printf("%d\n", N::a);printf("%d\n", b);return 0;
}
我们在第一次写c++程序时,都会写
#include<iostream>
using namespace std;
这是因为C++中的标准库都放在一个叫std的命名空间中,如果我们想要使用cout输出就需要在cout前面加上std::。如果我们增加了第二行代码,就把std这个命名空间展开了,使用里面的东西就不需要加上std::了。
注意:展开命名空间中全部成员,项目部推荐,冲突风险很⼤,日常的练习程序为了方便推荐使用。
二、C++输入和输出
- <iostream> 是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。
- std::cin 是 istream 类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。
- std::cout 是 ostream 类的对象,它注意面向窄字符的标准输出流。
- std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换行字符加刷新缓冲区。
- <<是流插⼊运算符,>>是流提取运算符。(C语言还⽤这两个运算符做位运算左移/右移)
- 使用C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要手动指定格式,C++的输⼊输出可以自动识别变量类型(本质是通过函数重载实现的),其实最重要的是 C++的流能更好的支持自定义类型对象的输入输出。
- cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。
- ⼀般日常练习中我们可以using namespace std,实际项目开发中不建议using namespace std。
- 下面例子我们没有包含<stdio.h>,也可以使⽤printf和scanf,在包含<iostream>间接包含了。vs系列编译器是这样的,其他编译器可能会报错。
#include <iostream>
using namespace std;
int main()
{int a = 0;double b = 0.1;char c = 'x';cout << a << " " << b << " " << c << endl;std::cout << a << " " << b << " " << c << std::endl;scanf("%d%lf", &a, &b);printf("%d %lf\n", a, b);// 可以⾃动识别变量的类型cin >> a;cin >> b >> c;cout << a << endl;cout << b << " " << c << endl;return 0;
}
在上面例子中,我们使用cin和cout来输入和输出变量,可以不用指定变量的类型了,因为c++自动的识别变量的类型,比c语言方便很多。
三、函数的缺省参数
- 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地⽅把缺省参数也叫默认参数)
- 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
- 带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
- 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
如下所示:
#include <iostream>
using namespace std;
void Func(int a = 0)
{cout << a << endl;
}
int main()
{Func(); // 没有传参时,使⽤参数的默认值Func(10); // 传参时,使⽤指定的实参return 0;
}
当我们没有给Func传入参数时,就会使用参数的默认值,当我们给Func传入参数时,就会使用我们传入的参数。
全缺省例子如下:
#include <iostream>
using namespace std;
// 全缺省
void Func1(int a = 10, int b = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;
}
int main()
{Func1();Func1(1);Func1(1,2);return 0;
}
当我们不传入参数调用Func1是,a和b都会使用默认值。
当我们传入一个参数时,第一个位置的a会使用我们传入的参数,b是默认值。必须从左到右依次给实参,不能跳跃给实参。
当我们传入两个参数时,a和b会依次读取我们传入的参数。
半缺省例子如下:
#include <iostream>
using namespace std;
// 半缺省
void Func1(int a, int b = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;
}
int main()
{Func1(1);Func1(1,2);return 0;
}
我们想要调用Func1时,可以传入一个参数,也可以传入两个参数。
当我们传入一个参数时,a会读取这个参数,b是默认值。
当我们传入两个参数时,a和b会依次读取我们传入的参数。
半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
四、函数重载
C++⽀持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态行为,使用更灵活。C语言是不⽀持支持同⼀作用域中出现同名函数的。
函数重载可以使函数名相同,提高复用性。
函数重载的条件:
- 在同一个作用域下
- 函数名称相同
- 函数的参数类型不同或参数个数不同或参数顺序不同。
参数类型不同如下所示:
#include<iostream>
using namespace std;
// 1、参数类型不同
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);return 0;
}
参数个数不同如下所示:
#include<iostream>
using namespace std;// 2、参数个数不同
void f()
{cout << "f()" << endl;
}
void f(int a)
{cout << "f(int a)" << endl;
}
int main()
{f();f(10);return 0;
}
参数顺序不同如下所示:
#include<iostream>
using namespace std;
// 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;
}
int main()
{f(10, 'a');f('a', 10);return 0;
}
注意1:当函数重载碰到默认参数时,会出现二义性,会报错,应该避免这样的情况发生。
注意2:函数是否返回值不能作为函数重载的条件。
五、引用
5.1 引用的概念和定义
引用不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同⼀块内存空间。
类型& 引用别名 = 引用对象;
如下所示:
#include<iostream>
using namespace std;
int main()
{int a = 0;// 引⽤:b和c是a的别名int& b = a;int& c = a;// 也可以给别名b取别名,d相当于还是a的别名int& d = b;++d;// 这⾥取地址我们看到是⼀样的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}
a,b,c,d指向的都是同一块空间.

5.2 引用的特性
- 引用在定义时必须初始化
- ⼀个变量可以有多个引用
- 引用⼀旦引用⼀个实体,再不能引用其他实体
如下所示:
#include<iostream>
using namespace std;
int main()
{int a = 10;// 编译报错:“ra”: 必须初始化引⽤//int& ra;int& b = a;int c = 20;// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,// 这⾥是⼀个赋值b = c;cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}
在上面的代码中,我们让b=c是把b的值改为了20,并没有将b变成c的别名。
5.3 引用的使用
- 引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。
- 引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
- 引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很⼤的区别的,除了用法,最⼤的点,C++引用定义后不能改变指向,Java的引用可以改变指向。
5.4 const引用
- 可以引用⼀个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。
如下所示:
#include<iostream>
using namespace std;
int main() {const int a = 10;/*int& ra = a;*/const int& ra = a;return 0;
}
注释的那一行代码编译时跑不过去的,这是因为我给a去一个别名,把权限放大了,变量a加了const修饰,只能读,不能改,给a取了一个别名ra,ra可以改a的值,会发生权限放大。
const引用也可以引用普通对象如下所示:
#include<iostream>
using namespace std;
int main() {int a = 10;const int& ra = a;return 0;
}
我们使用const引用给a取别名,当我们使用ra这个别名时,只能读,不能修改,当我们使用使用a时,可以读也能修改。这里的引用就是对b访问权限的缩小。
- 需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这里就触发了权限放大,必须要⽤常引用才可以。
-
所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。类型转换,运算求值,函数传参和函数返回都会产生临时变量,我们给这些临时变量增加引用时,需要const引用。
5.5 指针和引用的关系
C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。
- 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
- 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
- 应用在初始化时引⽤⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
- 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
- sizeof中含义不同,引⽤结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
- 指针很容易出现空指针和野指针的问题,引用很少出现,引用适宜起来相对更安全⼀些。