[C.C++] C++基础篇

861 0
Honkers 2025-5-28 06:49:51 来自手机 | 显示全部楼层 |阅读模式

目录

1、命名空间

2、缺省参数

3、函数重载 

4、引用

5、nullptr 和 inline

补充一下宏函数


1、命名空间

先看一段代码:

  1. #include<stdio.h>
  2. void f1()
  3. {
  4. printf("f1\n");
  5. }
  6. int f1 = 1;
  7. int main()
  8. {
  9. printf("%d\n", f1); //我想打印整型变量f1,可是编译报错
  10. return 0;
  11. }
复制代码

上面代码变量名冲突(一个是函数变量,一个是整型变量),而且这两者都在同一作用域下(全局作用域),打印f1时编译器就不知道要打印哪个f1。多个人写同一个项目时,变量名冲突也是常有的事。要是能把名字相同的划分到不同区域就好了。"哎,我有一计!",C++祖师爷搞出了命名空间namespace!命名空间就是在全局作用域划分出各自独立的区域

  1. #include<stdio.h>
  2. namespace A
  3. {
  4. void f1()
  5. {
  6. printf("f1\n");
  7. }
  8. } //注:这里不像结构体那样,无需加分号
  9. namespace B
  10. {
  11. int f1 = 1;
  12. } //注:这里不像结构体那样,无需加分号
  13. int main()
  14. {
  15. printf("%d\n", B::f1); //指定打印 B 作用域下的f1
  16. printf("%p", A::f1); //指定打印 A 作用域下的f1 (这里的f1是函数名,函数名就是指针)
  17. return 0;
  18. };
复制代码

上面代码将函数f1分到A 作用域,整型f1划分到B作用域。要使用f1的话就要指明具体位置。要用A里面的f1,就在f1前加上A::(我们称之为加域作用限定符)。用法: 作用域 : : 变量名。

  1. #include<stdio.h>
  2. int f1 = 999;
  3. int main()
  4. {
  5. char f1 = 'f';
  6. printf("%c\n", f1); //这里打印f1默认是打印当前作用域(main函数里面的)下的f1
  7. printf("%d\n", ::f1);
  8. //我想打印全局变量f1,就要指明全局作用域。全局作用域没用名字,直接加两个冒号就行
  9. return 0;
  10. }
复制代码

特例:宏定义无需指明作用域

  1. #include<iostream>
  2. using namespace std;
  3. namespace Wang
  4. {
  5. #define N 100
  6. #define ADD(a, b) ((a)+(b))
  7. }
  8. int main()
  9. {
  10. char arr[N]; //不可写成 char arr[Wang::N];
  11. int a = 1, b = 2;
  12. cout << ADD(a, b) << endl;
  13. //cout << Wang::ADD(a, b) << endl; // err
  14. return 0;
  15. }
复制代码

再看一段代码: 

上述代码中,我把Add函数放在C作用域里,使用的时候编译器不会自动去namespace作用域去找的,必须声明作用域。改为C::Add(a,b)即可。当然也可以展开命名空间

或者只展开部分成员 

名字相同的命名空间会合并

  1. #include<iostream>
  2. using namespace std;
  3. namespace A //下面还有一个A命名空间
  4. {
  5. int f1 = 10;
  6. }
  7. namespace A
  8. {
  9. int Add(int x, int y)
  10. {
  11. return x + y;
  12. }
  13. }
  14. /*上面两个会合并成下面这样
  15. namespace A
  16. {
  17. int f1 = 10;
  18. int Add(int x, int y)
  19. {
  20. return x + y;
  21. }
  22. }
  23. */
  24. int main()
  25. {
  26. cout << A::f1 << endl;
  27. cout << A::Add(1, 2) << endl;
  28. return 0;
  29. }
复制代码

注:命名空间只是在全局作用域划分,不可以在局部作用域划分。命名空间的变量跟全局变量都位于静态区,变量的值默认都是0。

  1. int a[3]; // 全局变量
  2. namespace Wang
  3. {
  4. int b[3]; // 命名空间的变量
  5. }
  6. int main()
  7. {
  8. int c[3]; // 局部变量,默认值不是0
  9. for (int i = 0; i < 3; ++i)
  10. cout << a[i] << " ";
  11. cout << endl;
  12. for (int i = 0; i < 3; ++i)
  13. cout << Wang::b[i] << " ";
  14. cout << endl;
  15. for (int i = 0; i < 3; ++i)
  16. cout << c[i] << " ";
  17. cout << endl;
  18. return 0;
  19. }
复制代码

 运行结果:

下面介绍一下C++的输入输出流。

输出:cout << 变量名【<<这里不是位运算的左移,而是流插入。cout会自动识别变量类型】

  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. cout << "Hello C++" << endl; //cout相当于printf函数,endl相当于换行'\n'
  6. //printf("Hello C++\n"); 上面那行跟此行的意思是等价的
  7. int a = 10;
  8. cout << a << endl;
  9. double b = 3.14;
  10. cout << b << endl;
  11. return 0;
  12. }
复制代码

cout、endl都是在命名空间std里的变量,如果不展开std 告诉编译器可以去里面找,那就要指明是哪里的cout(如下面的代码)。

  1. #include<iostream>
  2. int main()
  3. {
  4. std::cout << "Hello C++" << std::endl; //指明是std里面的cout、endl
  5. return 0;
  6. }
复制代码

输入:cin >> 变量名【>>这里不是位运算的右移,而是流提取,变量名前不用加&符号。与cout类似,cin也会自动识别类型】

  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int x, y;
  6. cin >> x >> y; //相当于 scanf("%d %d", &x, &y);
  7. cout << x + y << endl;
  8. char c[10];
  9. cin >> c; // 读取字符串,相当于 scanf("%s", c);
  10. cout << c << endl;
  11. return 0;
  12. }
复制代码

2、缺省参数

【给函数形参一个默认值】

调用func时,不传参时就用默认的那个,传参了就用你传的那个。

缺省参数分类:

1、全缺省(每个形参都给缺省值)

  1. #include<iostream>
  2. using namespace std;
  3. void func(int a = 10, int b = 20, int c = 30)
  4. {
  5. cout << "a:" << a << endl;
  6. cout << "b:" << b << endl;
  7. cout << "c:" << c << endl << endl;
  8. }
  9. int main()
  10. {
  11. func(); //无参,都用默认值
  12. func(999); //只传一个参数,按从左往右顺序传,所以传给第一个形参a
  13. func(111, 222); //111传给第一个形参a,222传给第二个形参b
  14. return 0;
  15. }
复制代码

传参是按从左往右的顺序传的,传一个参数,剩下的两个参数就用默认值;传两个参数,剩下的一个就用默认值。

2、半缺省

注意:

  • 1. 半缺省参数必须从右往左依次来给出不能间隔着给
  • 2. 缺省参数不能在函数声明和定义中同时出现
  • 3. 缺省值必须是常量或者全局变量
  • 4. C语言不支持(编译器不支持)

声明跟定义都给了缺省值

如果非要将函数声明跟定义分开写,缺省值应当在函数声明中给!

3、函数重载 

【C++允许在同一作用域中存在同名函数

1、参数类型不同

  1. #include<iostream>
  2. using namespace std;
  3. //参数类型是int
  4. int Add(int a, int b) //跟下面的Add构成函数重载
  5. {
  6. cout << "int Add(int a, int b)" << endl; //这里方便观察调用了哪个函数
  7. return a + b;
  8. }
  9. //参数类型是double
  10. double Add(double a, double b)
  11. {
  12. cout << "double Add(double a, double b)" << endl;
  13. return a + b;
  14. }
  15. int main()
  16. {
  17. int x1 = 2, y1 = 1;
  18. int z1 = Add(x1, y1);
  19. double x2 = 3.14, y2 = 9.1;
  20. double z2 = Add(x2, y2);
  21. return 0;
  22. }
复制代码

运行结果: 

可见x1、y1调用的时int类型的Add,x2、y2调用的是double类型的Add

2、参数个数不同 

  1. void f() //无参
  2. {
  3. cout << "void f()" << endl;
  4. }
  5. void f(int a) //一个参数
  6. {
  7. cout << "void f(int a)" << endl;
  8. }
  9. void f(int a, int b) //两个参数
  10. {
  11. cout << "void f(int a, int b)" << endl;
  12. }
  13. int main()
  14. {
  15. f();
  16. f(1);
  17. f(1, 2);
  18. return 0;
  19. }
复制代码

运行结果: 

3、参数顺序不同

  1. void f(int a, char b)
  2. {
  3. cout << "f(int a,char b)" << endl;
  4. }
  5. void f(char b, int a)
  6. {
  7. cout << "f(char b, int a)" << endl;
  8. }
  9. int main()
  10. {
  11. f(10, 'a');
  12. f('a', 10);
  13. return 0;
  14. }
复制代码

运行结果:

函数重载必须满足:

1、同一作用域下

2、函数名相同

3、参数个数、参数类型、参数顺序 这三者中至少有一个不同

  注:只有返回值类型不同时,不能构成函数重载

 此外,作用域不同也不构成重载

4、引用

引用是给已存在变量取了一个别名(或者说是"外号"),会共用同一快空间.

eg:李逵,在家称为"铁牛",江湖上人称"黑旋风"。(铁牛、黑旋风、李逵都是同一个人)

用法:类型&  引用变量名 = 引用实体;

  1. int main()
  2. {
  3. int a = 10;
  4. //int& b; //引用没有初始化,会报错
  5. int& p = a; //这里给变量a取了个别名p
  6. int& m = a; //又给a取了别名m
  7. ++p;
  8. ++m;
  9. cout << "a:" << a << endl;
  10. cout << "p:" << p << endl;
  11. cout << "m:" << m << endl;
  12. printf("a地址:%p\n", &a);
  13. printf("p地址:%p\n", &p);
  14. printf("m地址:%p\n", &m);
  15. int& ppp = p; //呕吼,也可以给引用变量取别名!本质都是给同一块空间命名
  16. cout << "ppp:" << ppp << endl;
  17. printf("ppp地址:%p\n", &ppp);
  18. return 0;
  19. }
复制代码

运行结果 :

 可以看出a、p、m、ppp的地址相同,他们都表示同一块空间。

【引用的时候类型要相匹配 】

  1. int main()
  2. {
  3. int a = 10;
  4. const int b = 1111;
  5. int& pa = a;
  6. //int& pb = b; //err b是const int类型,不能用int&
  7. const int& pb = b;
  8. double d = 2.1;
  9. //int& pd = d; //err 一个是int,一个是double。但是在前面加上const就可以
  10. const int& pd = d; // 原理详情:常引用
  11. double& pd = d;
  12. return 0;
  13. }
复制代码

特例:const修饰的引用可以引用非const类型(权限缩小)。【但非const修饰的引用不可以引用const类型(权限放大)】

  1. int main()
  2. {
  3. int a = 10;
  4. const int& pa = a; //const int& 可以引用 int (权限缩小)
  5. const int b = 111;
  6. int& pb = b; //err。 int& 不可以引用 const int (权限放大)
  7. return 0;
  8. }
复制代码

 【引用定义时必须初始化

【 一旦引用了一个变量,就不能引用其他变量】

  1. int main()
  2. {
  3. int a = 0, b = 111;
  4. int& p = a; //这里p引用了变量a
  5. //我突然又想让p引用b,然后写下了下面那行
  6. p = b;
  7. //很可惜不行。这里不是p又引用了变量b,而是将b赋值给p
  8. cout << "a:" << a << endl;
  9. cout << "p:" << p << endl;
  10. cout << "b:" << b << endl;
  11. return 0;
  12. }
复制代码

总结引用特性:

  1. 引用在定义时必须初始化。 【这一点跟常量类似】
  2.  一个变量可以有多个引用。
  3.  引用一旦引用一个实体,再不能引用其他实体。
  4. 权限可以平移、缩小,但不可以放大。【权限平移就是类似 int& 引用 int (同类型引用)】

常引用【给常量取别名】

先看一段代码:

  1. double d = 3.14;
  2. int b = d;
复制代码

d赋值给b是怎么完成的? (毕竟浮点数跟整型的存储方式不一样) 其过程如下:

临时空间具有常性 

  1. //等价于下面这段代码
  2. double d = 3.14;
  3. const int tmp = (int)d;
  4. int b = tmp;
复制代码

再来看之前的疑问: 为什么const int& 可以引用 double,而int& 不可引用 double ?

  1. double d = 3.14;
  2. int& b = d;
  3. const int& a = d;
  4. //等价于下面
  5. double d = 3.14;
  6. const int tmp = (int)d;
  7. int& b = tmp; //很明显int& 不能引用const int
  8. const int& a = tmp;
复制代码

涉及到运算的(或者类型转换)都会产生临时对象

  1. int main()
  2. {
  3. int a = 1, b = 2;
  4. //int& sum = a + b; //err a+b的结果保存在临时变量中
  5. const int& sum = a + b;
  6. //int& ret = a * b; //err a*b的结果保存在临时变量中
  7. const int& ret = a * b;
  8. char c = 'a';
  9. //int& pc = c; //err 编译器会开一块临时空间,将c转换成int类型后放到那块空间里
  10. const int& pc = c;
  11. //int& ppc = (int)c; //err
  12. const int& ppc = (int)c;
  13. return 0;
  14. }
复制代码

补:

  1. int& a = 10; //err 10是常量,给常量取别名要加const
  2. const int& a = 10;
复制代码

引用传参 【可减少拷贝,提高效率】

例如swap函数

  1. #include<iostream>
  2. using namespace std;
  3. void Swap(int& a, int& b) //这里相当于a是x的别名,b是y的别名
  4. {
  5. int tmp = a;
  6. a = b;
  7. b = tmp;
  8. }
  9. int main()
  10. {
  11. int x = 1, y = 2;
  12. Swap(x, y);
  13. cout << "x:" << x << endl;
  14. cout << "y:" << y << endl;
  15. return 0;
  16. }
复制代码

指针与引用的关系

  1. int main()
  2. {
  3. int a = 10, b = 1;
  4. int& pa = a; //这里跟下面的p有相似之处:必须初始化,都不可修改指向
  5. int* const p = &a;
  6. //p = &b; //err const在*后面,修饰的是p,表示p不可指向其他变量
  7. cout << sizeof(pa) << endl; //4 pa就是整型,里面存的10
  8. cout << sizeof(p) << endl; //8 p是指针,里面存的是a的地址
  9. //这里我是在64为环境下运行的。如果在32位环境下结果应该是4
  10. /*补:
  11. int const* ptr = &a;
  12. *ptr = 999; //err const在*之前,修饰的是*p,表示p指向的内容不可修改
  13. */
  14. return 0;
  15. }
复制代码
  1. #include<iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int arr[] = { 1,2,3 };
  6. int* ptr = &arr[0];
  7. int& r = arr[0];
  8. ptr++; //往后移,指向arr[1]
  9. r++; //相当于arr[0]++
  10. return 0;
  11. }
复制代码
  • 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
  • 引用在定义时必须初始化,指针只是建议初始化
  • 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
  • sizeof中含义不同,计算引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8字节)

5、nullptr 和 inline

nullptr

  1. void func(int a) //这里的func参数类型跟下面的不同,构成函数重载
  2. {
  3. cout << "void func(int a)" << endl;
  4. }
  5. void func(void* ptr)
  6. {
  7. cout << "void func(void* ptr)" << endl;
  8. }
  9. int main()
  10. {
  11. func(NULL);
  12. func(nullptr); //nullptr是空指针,是对NULL进行的优化
  13. return 0;
  14. }
复制代码

运行结果:

原因是 在库里对NULL是这样定义的:#define NULL 0; 本质上是宏替换,即func(NULL);变成了func(0);所以才会调用参数位int的那个func。在C++中 空指针更建议用nullptr

inline

对于一些频繁调用的小函数,例如swap,频繁调用会不断建立函数栈帧,开销会加大

  1. #include<iostream>
  2. using namespace std;
  3. void Swap(int& a, int& b)
  4. {
  5. int tmp = a;
  6. a = b;
  7. b = tmp;
  8. }
  9. int main()
  10. {
  11. int arr[] = { 6,2,4,21,8,5,7,10,45 };
  12. int n = sizeof(arr) / sizeof(int);
  13. //冒泡排序,升序
  14. for (int i = n - 1; i > 0 ; --i)
  15. {
  16. for (int j = 0; j < i; ++j)
  17. {
  18. if (arr[j] > arr[j + 1])
  19. Swap(arr[j], arr[j + 1]); //频繁调用Swap
  20. }
  21. }
  22. return 0;
  23. }
复制代码

这时就可以用inline【当然也可以用宏,但宏函数贼容易出错】

⽤inline修饰的函数叫做内联函数。普通函数调用时会建立函数栈帧,inline修饰后表示建议这个函数无需建立栈帧。(但只是建议,编译器听不听取决于它。如果函数中的语句较多,可能就会跟普通函数调用那样建立栈帧)。不建立栈帧就相当于直接展开函数,可以提高效率。

用法如下

  1. inline void func()
  2. {
  3. //...
  4. }
复制代码

 注:内联函数不可声明跟定义分离【内联函数没有地址,链接不了】

补充一下宏函数

我想用宏写一个加法函数。下面有四种写法:

  1. #define ADD(x, y) x + y; //A
  2. #define ADD(x, y) x + y //B
  3. #define ADD(x, y) (x + y) //C
  4. #define ADD(x, y) ((x) + (y)) //D
复制代码

第四个的是对的 

  1. //*****对于A
  2. #define ADD(x, y) x + y;
  3. cout << ADD(1, 2) << endl; //会在预处理阶段替换成下面那行代码
  4. // cout << 1 + 2; << endl; //很明显中间的分号有问题
复制代码
  1. ///*****对于B
  2. #define ADD(x, y) x + y
  3. //我想计算(1+2)*4 结果应该是12
  4. cout << ADD(1, 2) * 4 << endl; //输出9 原因是宏替换成了下面的代码,*优先级高于+
  5. // cout << 1 + 2 * 4 << endl;
复制代码
  1. //****对与C,遇到比+优先级低的运算符时会出错,比如&&、||
  2. #define ADD(x, y) (x + y)
  3. int x = 1, y = 1; //x && y = 1, x || y = 1, 1 + 1 = 2, 结果应该是2
  4. cout << ADD(x && y, x || y) << endl; //输出1,因为被替换成下面的语句了
  5. //cout << (x && y + x || y) << endl; //加法优先级搞,等价于 x && (y + x) || y,所以输出1
复制代码

光用宏写一个加法函数就要注意那么多细节,稍不留意就会有各种bug,而且还不好调试。这时可以在普通函数前加上inline。【为了提高效率,较为简单的函数都可以加上inline】

  1. inline Add(int x, int y)
  2. {
  3. return x + y;
  4. }
复制代码

好了,以上就是全部内容了,下期见~

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行