【C++】C++入门(上)

简介: 【C++】C++入门(上)

一、C++ 关键字(C++98)

C++ 总计 63 个关键字,C 语言 32 个关键字。C 语言的关键字在 C++ 中继续可以使用。 C++ 兼容 C 的绝大多数语法。


二、命名空间

在 C / C++ 中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是 对标识符的名称进行本地化,以避免命名冲突或名字污染 ,因为 C 语言没办法解决类似这样的命名冲突问题,所以 C++ 提出了 namespace 来解决,namespace 关键字的出现就是针对这种问题的。

如果没有包含 stdlib.h 头文件,代码可以正常运行;但包含之后,因为 stdlib.h 库中有一个叫 rand 函数,全局变量 rand 与其冲突了。

#include <stdio.h>
#include <stdlib.h> // rand
 
int rand = 10; // 全局变量rand
 
int main()
{
    printf("%d\n", rand);
    return 0;
}
// 编译后报错:error C2365: “rand”: 重定义;以前的定义是“函数”
  • 不同的作用域中,可以定义同名的变量
  • 但在同一作用域下,不能定义同名的变量

用命名空间来解决变量 rand 和 stdlib 库里的命名冲突。

#include <stdio.h>
#include <stdlib.h>
 
namespace xyl // 定义了一个命名空间域
{
  int rand = 10; // 定义变量
  int Add(int x, int y) // 定义函数
  {
    return x + y; 
  }
  struct Node // 定义结构体类型
  {
    struct Node* next;
    int val;  
  };
}
 
int main()
{
  printf("%p\n", rand);//函数指针
  printf("%d\n", xyl::rand); // rand变量;'::'叫做域作用限定符
  xyl::Add(3, 5);//调用函数
  struct xyl::Node node1; // 结构体  
  return 0;
}

1、命名空间的定义

定义命名空间,需要使用到 namespace 关键字,后面 + 命名空间的名字 ,然后 接一对 {}  即可,{} 中即为命名空间的 成员

(1)正常的命名空间定义
namespace xyl
{
    int rand = 10; // 命名空间中可以定义变量
 
    int Add(int left, int right) // 命名空间中可以定义函数
    {
        return left + right;
    }
 
    struct Node // 命名空间中可以定义类型
    {
        int val;
        struct Node* next;
    };
}

xyl 是命名空间的名字,一般开发中是用项目名字做命名空间名。


(2)命名空间可以嵌套
// test.cpp
namespace N1
{
    int a;
    int b;
    int Add(int left, int right)
    {
        return left + right;
    }
 
    namespace N2
    {
        int c;
        int d;
        int Sub(int left, int right)
        {
            return left - right;
        }
    }
}
 
int main()
{
  printf("%d\n", N1::N2::Sub(10, 20)); // 访问嵌套命名空间
  return 0;
}

(3)同一个工程中允许存在多个相同名称的命名空间
// test.h
namespace N1
{
    int Mul(int left, int right)
    {
        return left * right;
    }
}

同一个工程中的 test.h 和上面 test.cpp 中的两个 N1 会被编译器合并成在同一个命名空间中。

注意一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。


2、命名空间的使用

命名空间的使用有三种方式:

(1)加命名空间名称及作用域限定符
int main()
{
    printf("%d\n", N::a);
    return 0;    
}

⚪优点:不存在命名污染。

⚪缺点:如果要去访问多个命名空间里的东西时,需要一一指定。


(2)使用 using 将命名空间中某个成员引入
using N::b; // 命名空间N中变量b用的非常多,将其展开到全局
 
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    return 0;    
}

⚪优点:不会造成大面积的污染;把常用的展开后,也不需要一一指定。


(3)使用 using namespace 将命名空间名称引入
//using namespace std; //std是包含C++标准库的命名空间
using namespace N;
 
int main()
{
    printf("%d\n", b);
    Add(10, 20);
    return 0;    
}

⚪优点:方便。

⚪缺点:自己定义的东西会暴露出去,导致命名污染。


(4)补充 -- 命名空间 std 的规范使用

C++ 把标准库里面的东西都放到命名空间 std 中,所以在实际开发中,为了避免你自己写的变量 / 函数 / 类型等等与标准库中的冲突,建议不要把命名空间 std 直接展开到全局,而是把常用的展开就行,一般规范的写法如下:

#include<iostream>
//using namespace std;  //不要将std直接展开到全局
 
//把常用的展开就行
using std::cout;
using std::endl;
 
int main()
{
  cout << "hello world" << endl;
  return 0;
}

如果是日常练习,就不需要像上面这么规范,直接展开用。


三、C++ 输入&输出

#include <iostream>
using namespace std;
 
int main()
{
    cin >> a;  // >> 输入运算符/流提取运算符
  cout << a; // << 输出运算符/流插入运算符
    cout << "Hello world!" << endl;
    return 0;
}

std 是 C++ 标准库的命名空间名,C++ 将标准库的定义实现都放到这个命名空间中。

  1. 使用 cout 标准输出对象(控制台)和 cin 标准输入对象(键盘)时,必须包含 头文件以及按命名空间使用方法使用 std
  2. cout 和 cin 类似 C 语言的 printf 和 scanf,这里先简单了解一下,因为对于 C 语言中的 I/O 是函数,而 C++ 是对象。cout 和 cin 是全局流对象endl 是特殊的 C++ 符号,表示换行输出,他们都包含在包含 <iostream> 头文件中。
  3. << 是流插入运算符,>> 是流提取运算符
  4. 使用 C++ 输入输出更方便,不需要像 printf / scanf 输入输出时那样,需要手动控制格式。
  5. C++ 的输入输出可以自动识别变量类型
  6. 实际上 cout 和 cin 分别是 ostream 和 istream 类型的对象,>> 和 << 也涉及运算符重载等知识。

注意 :早期标准库将所有功能在全局域中实现,声明在 .h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在 std 命名空间下,为了和 C 头文件区分,也为了正确使用命名空间,规定 C++ 头文件不带 .h ;旧编译器 (vc 6.0) 中还支持  格式,后续编译器已不再支持,所以推荐使用 + std 的方式

#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;
}

关于 cout 和 cin 还有很多更复杂的用法,比如控制浮点数输出精度,控制整形输出进制格式等。但是因为 C++ 是兼容 C 语言的用法,这些又用得不是很多,所以我们一般可以直接用按照 C 语言的用法来写,就不用再进一步学习 C++ 这方面的内容了。

注意:C++ 中的浮点数,当小数点后数字超过 5 位时,cout 规定输出的是小数点后 5 位,如果想要实现浮点数的格式化输出,那就建议用 printf 来输出,使用一定要灵活变通。

std 命名空间的使用惯例:

std 是 C++ 标准库的命名空间,如何展开 std 使用更合理呢?
  1. 日常练习中,建议直接 using namespace std 即可,这样就很方便。
  2. using namespace std 展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型 / 对象 / 函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现这种情况。所以建议在项目开发中使用,像 std::cout 这样使用时指定命名空间 + using std::cout 展开常用的库对象 / 类型等方式
#include<iostream>
 
// 展开常用即可,工程项目中常见的对命名空间的用法
using std::cout;
using std::endl;
 
int main()
{
  // 只要是库里的都得指定std
  std::cout << "Hello world!" << std::endl
  //cout << "Hello world!" << endl;
  return 0;
}

四、缺省参数

1、概念

缺省参数 声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参

void Func(int a = 0)
{
    cout << a << endl;
}
 
int main()
{
    Func();   // 没有传参时,使用参数的默认值 - 0
    Func(10); // 传参时,使用指定的实参 - 10
    return 0;
}

2、分类

(1)全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{
  cout << a + b + c << endl;
}
 
int main()
{
  // 调用全缺省参数的函数方式很灵活
  Func();
  Func(1);
  Func(1, 2);
  Func(1, 2, 3);
  return 0;
}

(2)半缺省参数
void Func(int a, int b=10, int c=20)
{
  cout << a + b + c << endl;
}
 
int main()
{
  // 调用半缺省参数的函数
  Func(1);       //1传给a
  Func(1, 2);    //1传给a,2传给b
  Func(1, 2, 3); //1传给a,2传给b,3传给c
  return 0;
}

注意

  1. 半缺省参数必须从右往左依次给出,不能间隔着给
  2. 缺省参数不能函数声明和定义同时出现
  3. 缺省值必须是 常量 或者 全局变量
  4. C 语言不支持(编译器不支持)。
// test.h
void Func(int a = 10);
  
// test.cpp
void Func(int a = 20)
{}

注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不相同,那么编译器就无法确定到底该用那个缺省值。


五、函数重载

1、概念

函数重载 :是函数的一种特殊情况,C++ 允许在同一作用域中声明几个功能类似同名函数,这些同名函数的形参列表(参数个数 / 类型 / 类型顺序)不同,常用来处理实现 功能类似数据类型不同 的问题。

(1)参数类型不同
// test1.cpp
#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.0, 20.0);
  return 0;
}

(2)参数个数不同
void f()
{
    cout << "f()" << endl;
}
 
void f(int a)
{
    cout << "f(int a)" << endl;
}

(3)参数类型顺序不同
// test2.cpp
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()
{
    Add(10, 20);
    Add(10.1, 20.2);
 
    f();
    f(10);
 
    f(10, 'a');
    f('a', 10);
    return 0;
}

2、C++ 支持函数重载的原理 -- 名字修饰(name Mangling)

为什么 C++ 支持函数重载,而 C语言 不支持函数重载呢?

在 C / C++ 中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

可参考【C语言】程序环境预处理 -- 详解_炫酷的伊莉娜的博客-CSDN博客 ,内有详细介绍。

       实际项目通常是由多个头文件和多个源文件构成,而通过 C 语言阶段学习的编译链接,我们 知道,当前 test2.cpp 中调用了 test1.cpp 中定义的 Add 函数,在编译后链接前,test2.o 的目标文件中没有 Add 的函数地址,因为 Add 是在 test1.cpp 中定义的,所以 Add 的地址在 test1.o 中,那该怎么办呢?

       所以链接阶段就是专门处理这种问题,链接器看到 test2.o 调用 Add,但是没有 Add 的地址,就会到 test1.o 的符号表中找 Add 的地址,然后链接到一起

       那么在链接时,面对 Add 函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。由于 Windows 下 VS 的修饰规则过于复杂,而 Linux 下 G++ 的修饰规则简单易懂,下面我们使用了 G++ 演示了这个修饰后的名字。

(1)采用 C 语言编译器编译后的结果

结论 :在 Linux 下,采用 GCC 编译完成后,函数名字的修饰没有发生改变函数名相同时,无法区分函数。


(2)采用 C++ 编译器编译后的结果

结论 在 Linux 下,采用 G++ 编译完成后,函数名字的修饰发生改变编译器将函数参 数类型信息添加到修改后的名字中

补充通过上面我们可以看出 GCC 的函数修饰后名字不变,而 G++ 的函数修饰后变成 <_Z + 函数长度 + 函数名 + 类型首字母>

以 Add 函数为例:

_Z 是 GCC 编译器的修饰前缀,表示这是一个 C++ 函数名。

3 是函数名的长度

Add 是函数名

ii / dd 是函数参数类型的首字母,如果是 int* i,那么就是 Pi。


(3)Windows 下名字的修饰规则


【总结】

通过这里就理解了 C 语言没办法支持重载,因为同名函数没办法区分。而 C++ 是通过函数修饰规则来区分只要参数不同,修饰出来的名字就不一样,就支持了重载。如果两个函数的函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。


(4)extern "C"

C++ 编译器能识别 C++ 函数名修饰规则,也能识别 C 的函数名修饰规则。

在 C++ 项目开发中,可能会用到一些第三方的库,有些是纯 C 实现的库(动态库 / 静态库),它里面的函数名修饰规则是 C 的,而 C++ 是兼容 C 的,C++ 编译器直接按照 C 的修饰规则去调用。

但如果是纯 C 的项目,用到 C++ 实现的库(动态库 / 静态库),比如:tcmalloc 是 Google 用 C++ 实现的一个库,他提供 tcmallc() 和 tcfree() 两个接口来使用(更高效,替代 malloc 和 free 函数),这个库可以给 C++ 项目用,但不能给 C 项目用,会存在链接失败,因为 C 编译器无法识别 C++ 的修饰规则。

如果想要给 C 用,需要将 C++ 库中的部分函数按照 C 的风格来编译。

// 在函数前加extern "C",意思是告诉编译器,将该函数按照C的修饰规则来编译
extern "C" int Add(int left, int right);
 
int main()
{
  Add(1,2);
  return 0; 
}
  1. C++ 项目可以调用 C++ 库,也可以调用 C 的库,C++ 是直接兼容 C 的。
  2. C 项目可以调用 C 库,也可以使用 extern "C" 调用 C++ 库C++ 提供的函数加上 extern "C")。

六、引用(重点)

1、引用概念

引用 不是新定义一个变量,而是给已 存在变量 取了一个 别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

类型& 引用变量名(对象名) = 引用实体;

注意:这里的 & 跟 C 语言中的取地址符号一样,但它们之间没有任何关联,各有各的用处。

// b和c是a的引用,a、b、c地址一样,共用同一块内存空间:
int main()
{
  int a = 10;
  int& b = a;
  int& c = a;
 
  cout << a << endl;
  cout << b << endl;
  cout << c << endl;
 
  return 0;
}

注意引用类型必须和引用实体同种类型的。


2、引用特性

  1. 引用定义时必须初始化
int& b;  // error
  1. 一个变量可以有多个引用
int a = 10;
int& b = a;
int& c = a;
  1. 引用一旦引用一个实体,就再不能引用其他的实体。
int main()
{ 
  int a = 10;
  int& b = a;
 
  int c = 20;
  b = c; // error
    
    return 0;
}


【C++】C++入门(下)https://developer.aliyun.com/article/1514551?spm=a2c6h.13148508.setting.22.4b904f0ejdbHoA

相关文章
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
47 2
C++入门12——详解多态1
|
2月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
39 3
|
2月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
51 2
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
89 1
|
2月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
92 1
|
2月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
31 1
|
2月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
51 1
|
2月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
70 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
2月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
30 0
|
2月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
39 0