目录
1、命名空间
2、缺省参数
3、函数重载
4、引用
5、nullptr 和 inline
补充一下宏函数
1、命名空间
先看一段代码: - #include<stdio.h>
- void f1()
- {
- printf("f1\n");
- }
- int f1 = 1;
- int main()
- {
- printf("%d\n", f1); //我想打印整型变量f1,可是编译报错
- return 0;
- }
复制代码
上面代码变量名冲突(一个是函数变量,一个是整型变量),而且这两者都在同一作用域下(全局作用域),打印f1时编译器就不知道要打印哪个f1。多个人写同一个项目时,变量名冲突也是常有的事。要是能把名字相同的划分到不同区域就好了。"哎,我有一计!",C++祖师爷搞出了命名空间namespace!命名空间就是在全局作用域划分出各自独立的区域 - #include<stdio.h>
- namespace A
- {
- void f1()
- {
- printf("f1\n");
- }
- } //注:这里不像结构体那样,无需加分号
- namespace B
- {
- int f1 = 1;
- } //注:这里不像结构体那样,无需加分号
- int main()
- {
- printf("%d\n", B::f1); //指定打印 B 作用域下的f1
- printf("%p", A::f1); //指定打印 A 作用域下的f1 (这里的f1是函数名,函数名就是指针)
- return 0;
- };
复制代码
上面代码将函数f1分到A 作用域,整型f1划分到B作用域。要使用f1的话就要指明具体位置。要用A里面的f1,就在f1前加上A::(我们称之为加域作用限定符)。用法: 作用域 : : 变量名。 - #include<stdio.h>
- int f1 = 999;
- int main()
- {
- char f1 = 'f';
- printf("%c\n", f1); //这里打印f1默认是打印当前作用域(main函数里面的)下的f1
- printf("%d\n", ::f1);
- //我想打印全局变量f1,就要指明全局作用域。全局作用域没用名字,直接加两个冒号就行
- return 0;
- }
复制代码
特例:宏定义无需指明作用域 - #include<iostream>
- using namespace std;
- namespace Wang
- {
- #define N 100
- #define ADD(a, b) ((a)+(b))
- }
- int main()
- {
- char arr[N]; //不可写成 char arr[Wang::N];
- int a = 1, b = 2;
- cout << ADD(a, b) << endl;
- //cout << Wang::ADD(a, b) << endl; // err
- return 0;
- }
复制代码
再看一段代码:
上述代码中,我把Add函数放在C作用域里,使用的时候编译器不会自动去namespace作用域去找的,必须声明作用域。改为C::Add(a,b)即可。当然也可以展开命名空间。
或者只展开部分成员
名字相同的命名空间会合并 - #include<iostream>
- using namespace std;
- namespace A //下面还有一个A命名空间
- {
- int f1 = 10;
- }
- namespace A
- {
- int Add(int x, int y)
- {
- return x + y;
- }
- }
- /*上面两个会合并成下面这样
- namespace A
- {
- int f1 = 10;
- int Add(int x, int y)
- {
- return x + y;
- }
- }
- */
- int main()
- {
- cout << A::f1 << endl;
- cout << A::Add(1, 2) << endl;
- return 0;
- }
复制代码
注:命名空间只是在全局作用域划分,不可以在局部作用域划分。命名空间的变量跟全局变量都位于静态区,变量的值默认都是0。 - int a[3]; // 全局变量
- namespace Wang
- {
- int b[3]; // 命名空间的变量
- }
- int main()
- {
- int c[3]; // 局部变量,默认值不是0
- for (int i = 0; i < 3; ++i)
- cout << a[i] << " ";
- cout << endl;
- for (int i = 0; i < 3; ++i)
- cout << Wang::b[i] << " ";
- cout << endl;
- for (int i = 0; i < 3; ++i)
- cout << c[i] << " ";
- cout << endl;
- return 0;
- }
复制代码
运行结果:
下面介绍一下C++的输入输出流。
输出:cout << 变量名【<<这里不是位运算的左移,而是流插入。cout会自动识别变量类型】
- #include<iostream>
- using namespace std;
- int main()
- {
- cout << "Hello C++" << endl; //cout相当于printf函数,endl相当于换行'\n'
- //printf("Hello C++\n"); 上面那行跟此行的意思是等价的
-
- int a = 10;
- cout << a << endl;
-
- double b = 3.14;
- cout << b << endl;
- return 0;
- }
复制代码
cout、endl都是在命名空间std里的变量,如果不展开std 告诉编译器可以去里面找,那就要指明是哪里的cout(如下面的代码)。 - #include<iostream>
- int main()
- {
- std::cout << "Hello C++" << std::endl; //指明是std里面的cout、endl
- return 0;
- }
复制代码
输入:cin >> 变量名【>>这里不是位运算的右移,而是流提取,变量名前不用加&符号。与cout类似,cin也会自动识别类型】
- #include<iostream>
- using namespace std;
- int main()
- {
- int x, y;
- cin >> x >> y; //相当于 scanf("%d %d", &x, &y);
- cout << x + y << endl;
- char c[10];
- cin >> c; // 读取字符串,相当于 scanf("%s", c);
- cout << c << endl;
- return 0;
- }
复制代码
2、缺省参数
【给函数形参一个默认值】
调用func时,不传参时就用默认的那个,传参了就用你传的那个。
缺省参数分类:
1、全缺省(每个形参都给缺省值)
- #include<iostream>
- using namespace std;
- void func(int a = 10, int b = 20, int c = 30)
- {
- cout << "a:" << a << endl;
- cout << "b:" << b << endl;
- cout << "c:" << c << endl << endl;
- }
- int main()
- {
- func(); //无参,都用默认值
- func(999); //只传一个参数,按从左往右顺序传,所以传给第一个形参a
- func(111, 222); //111传给第一个形参a,222传给第二个形参b
- return 0;
- }
复制代码
传参是按从左往右的顺序传的,传一个参数,剩下的两个参数就用默认值;传两个参数,剩下的一个就用默认值。
2、半缺省
注意:
- 1. 半缺省参数必须从右往左依次来给出,不能间隔着给
- 2. 缺省参数不能在函数声明和定义中同时出现
- 3. 缺省值必须是常量或者全局变量
- 4. C语言不支持(编译器不支持)
声明跟定义都给了缺省值
如果非要将函数声明跟定义分开写,缺省值应当在函数声明中给!
3、函数重载
【C++允许在同一作用域中存在同名函数】
1、参数类型不同
- #include<iostream>
- using namespace std;
- //参数类型是int
- int Add(int a, int b) //跟下面的Add构成函数重载
- {
- cout << "int Add(int a, int b)" << endl; //这里方便观察调用了哪个函数
- return a + b;
- }
- //参数类型是double
- double Add(double a, double b)
- {
- cout << "double Add(double a, double b)" << endl;
- return a + b;
- }
- int main()
- {
- int x1 = 2, y1 = 1;
- int z1 = Add(x1, y1);
- double x2 = 3.14, y2 = 9.1;
- double z2 = Add(x2, y2);
- return 0;
- }
复制代码
运行结果:
可见x1、y1调用的时int类型的Add,x2、y2调用的是double类型的Add
2、参数个数不同
- void f() //无参
- {
- cout << "void f()" << endl;
- }
- void f(int a) //一个参数
- {
- cout << "void f(int a)" << endl;
- }
- void f(int a, int b) //两个参数
- {
- cout << "void f(int a, int b)" << endl;
- }
- int main()
- {
- f();
- f(1);
- f(1, 2);
- return 0;
- }
复制代码
运行结果:
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、函数名相同
3、参数个数、参数类型、参数顺序 这三者中至少有一个不同
注:只有返回值类型不同时,不能构成函数重载
此外,作用域不同也不构成重载
4、引用
引用是给已存在变量取了一个别名(或者说是"外号"),会共用同一快空间.
eg:李逵,在家称为"铁牛",江湖上人称"黑旋风"。(铁牛、黑旋风、李逵都是同一个人)
用法:类型& 引用变量名 = 引用实体; - int main()
- {
- int a = 10;
- //int& b; //引用没有初始化,会报错
- int& p = a; //这里给变量a取了个别名p
- int& m = a; //又给a取了别名m
- ++p;
- ++m;
- cout << "a:" << a << endl;
- cout << "p:" << p << endl;
- cout << "m:" << m << endl;
- printf("a地址:%p\n", &a);
- printf("p地址:%p\n", &p);
- printf("m地址:%p\n", &m);
- int& ppp = p; //呕吼,也可以给引用变量取别名!本质都是给同一块空间命名
- cout << "ppp:" << ppp << endl;
- printf("ppp地址:%p\n", &ppp);
- return 0;
- }
复制代码
运行结果 :
可以看出a、p、m、ppp的地址相同,他们都表示同一块空间。
【引用的时候类型要相匹配 】 - int main()
- {
- int a = 10;
- const int b = 1111;
- int& pa = a;
- //int& pb = b; //err b是const int类型,不能用int&
- const int& pb = b;
- double d = 2.1;
- //int& pd = d; //err 一个是int,一个是double。但是在前面加上const就可以
- const int& pd = d; // 原理详情:常引用
- double& pd = d;
- return 0;
- }
复制代码
特例:const修饰的引用可以引用非const类型(权限缩小)。【但非const修饰的引用不可以引用const类型(权限放大)】 - int main()
- {
- int a = 10;
- const int& pa = a; //const int& 可以引用 int (权限缩小)
- const int b = 111;
- int& pb = b; //err。 int& 不可以引用 const int (权限放大)
- return 0;
- }
复制代码
【引用定义时必须初始化】
【 一旦引用了一个变量,就不能引用其他变量】 - int main()
- {
- int a = 0, b = 111;
- int& p = a; //这里p引用了变量a
- //我突然又想让p引用b,然后写下了下面那行
- p = b;
- //很可惜不行。这里不是p又引用了变量b,而是将b赋值给p
- cout << "a:" << a << endl;
- cout << "p:" << p << endl;
- cout << "b:" << b << endl;
- return 0;
- }
复制代码
总结引用特性:
- 引用在定义时必须初始化。 【这一点跟常量类似】
- 一个变量可以有多个引用。
- 引用一旦引用一个实体,再不能引用其他实体。
- 权限可以平移、缩小,但不可以放大。【权限平移就是类似 int& 引用 int (同类型引用)】
常引用【给常量取别名】
先看一段代码: - double d = 3.14;
- int b = d;
复制代码
d赋值给b是怎么完成的? (毕竟浮点数跟整型的存储方式不一样) 其过程如下:
临时空间具有常性 - //等价于下面这段代码
- double d = 3.14;
- const int tmp = (int)d;
- int b = tmp;
复制代码
再来看之前的疑问: 为什么const int& 可以引用 double,而int& 不可引用 double ? - double d = 3.14;
- int& b = d;
- const int& a = d;
- //等价于下面
- double d = 3.14;
- const int tmp = (int)d;
- int& b = tmp; //很明显int& 不能引用const int
- const int& a = tmp;
复制代码
涉及到运算的(或者类型转换)都会产生临时对象 - int main()
- {
- int a = 1, b = 2;
- //int& sum = a + b; //err a+b的结果保存在临时变量中
- const int& sum = a + b;
- //int& ret = a * b; //err a*b的结果保存在临时变量中
- const int& ret = a * b;
- char c = 'a';
- //int& pc = c; //err 编译器会开一块临时空间,将c转换成int类型后放到那块空间里
- const int& pc = c;
- //int& ppc = (int)c; //err
- const int& ppc = (int)c;
- return 0;
- }
复制代码
补: - int& a = 10; //err 10是常量,给常量取别名要加const
- const int& a = 10;
复制代码
引用传参 【可减少拷贝,提高效率】
例如swap函数 - #include<iostream>
- using namespace std;
- void Swap(int& a, int& b) //这里相当于a是x的别名,b是y的别名
- {
- int tmp = a;
- a = b;
- b = tmp;
- }
- int main()
- {
- int x = 1, y = 2;
- Swap(x, y);
- cout << "x:" << x << endl;
- cout << "y:" << y << endl;
- return 0;
- }
复制代码
指针与引用的关系 - int main()
- {
- int a = 10, b = 1;
- int& pa = a; //这里跟下面的p有相似之处:必须初始化,都不可修改指向
- int* const p = &a;
- //p = &b; //err const在*后面,修饰的是p,表示p不可指向其他变量
-
- cout << sizeof(pa) << endl; //4 pa就是整型,里面存的10
- cout << sizeof(p) << endl; //8 p是指针,里面存的是a的地址
- //这里我是在64为环境下运行的。如果在32位环境下结果应该是4
- /*补:
- int const* ptr = &a;
- *ptr = 999; //err const在*之前,修饰的是*p,表示p指向的内容不可修改
- */
- return 0;
- }
复制代码- #include<iostream>
- using namespace std;
- int main()
- {
- int arr[] = { 1,2,3 };
- int* ptr = &arr[0];
- int& r = arr[0];
- ptr++; //往后移,指向arr[1]
- r++; //相当于arr[0]++
- return 0;
- }
复制代码
- 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
- 引用在定义时必须初始化,指针只是建议初始化
- 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
- sizeof中含义不同,计算引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8字节)
5、nullptr 和 inline
nullptr
- void func(int a) //这里的func参数类型跟下面的不同,构成函数重载
- {
- cout << "void func(int a)" << endl;
- }
- void func(void* ptr)
- {
- cout << "void func(void* ptr)" << endl;
- }
- int main()
- {
- func(NULL);
- func(nullptr); //nullptr是空指针,是对NULL进行的优化
- return 0;
- }
复制代码
运行结果:
原因是 在库里对NULL是这样定义的:#define NULL 0; 本质上是宏替换,即func(NULL);变成了func(0);所以才会调用参数位int的那个func。在C++中 空指针更建议用nullptr
inline
对于一些频繁调用的小函数,例如swap,频繁调用会不断建立函数栈帧,开销会加大
- #include<iostream>
- using namespace std;
- void Swap(int& a, int& b)
- {
- int tmp = a;
- a = b;
- b = tmp;
- }
- int main()
- {
- int arr[] = { 6,2,4,21,8,5,7,10,45 };
- int n = sizeof(arr) / sizeof(int);
- //冒泡排序,升序
- for (int i = n - 1; i > 0 ; --i)
- {
- for (int j = 0; j < i; ++j)
- {
- if (arr[j] > arr[j + 1])
- Swap(arr[j], arr[j + 1]); //频繁调用Swap
- }
- }
- return 0;
- }
复制代码
这时就可以用inline【当然也可以用宏,但宏函数贼容易出错】
⽤inline修饰的函数叫做内联函数。普通函数调用时会建立函数栈帧,inline修饰后表示建议这个函数无需建立栈帧。(但只是建议,编译器听不听取决于它。如果函数中的语句较多,可能就会跟普通函数调用那样建立栈帧)。不建立栈帧就相当于直接展开函数,可以提高效率。
用法如下
- inline void func()
- {
- //...
- }
复制代码
注:内联函数不可声明跟定义分离【内联函数没有地址,链接不了】
补充一下宏函数
我想用宏写一个加法函数。下面有四种写法: - #define ADD(x, y) x + y; //A
- #define ADD(x, y) x + y //B
- #define ADD(x, y) (x + y) //C
- #define ADD(x, y) ((x) + (y)) //D
复制代码
第四个的是对的 - //*****对于A
- #define ADD(x, y) x + y;
- cout << ADD(1, 2) << endl; //会在预处理阶段替换成下面那行代码
- // cout << 1 + 2; << endl; //很明显中间的分号有问题
复制代码- ///*****对于B
- #define ADD(x, y) x + y
- //我想计算(1+2)*4 结果应该是12
- cout << ADD(1, 2) * 4 << endl; //输出9 原因是宏替换成了下面的代码,*优先级高于+
- // cout << 1 + 2 * 4 << endl;
复制代码- //****对与C,遇到比+优先级低的运算符时会出错,比如&&、||
- #define ADD(x, y) (x + y)
- int x = 1, y = 1; //x && y = 1, x || y = 1, 1 + 1 = 2, 结果应该是2
- cout << ADD(x && y, x || y) << endl; //输出1,因为被替换成下面的语句了
- //cout << (x && y + x || y) << endl; //加法优先级搞,等价于 x && (y + x) || y,所以输出1
复制代码
光用宏写一个加法函数就要注意那么多细节,稍不留意就会有各种bug,而且还不好调试。这时可以在普通函数前加上inline。【为了提高效率,较为简单的函数都可以加上inline】 - inline Add(int x, int y)
- {
- return x + y;
- }
复制代码
好了,以上就是全部内容了,下期见~ |