Skip to content

Latest commit

 

History

History
415 lines (345 loc) · 12.3 KB

l_08.md

File metadata and controls

415 lines (345 loc) · 12.3 KB

Lesson8 命名空间&输入输出流对象&字符串&引用



0. 前言

  • 本课程面向已经学习完C语言大部分内容(指针,内存,结构体)的同学,如没有学习完成,请在课程开始前学习Lesson1-Lesson7,重点学习Lesson3&6。

C++继承了C语言大部分特性,并且对C语言中部分反人类的设定做了很多优化; C++在保留C语言指针特性的前提下,新增了两大方向:类和模板,当然也会有两者相交织的地方,它们分别对应着面向对象编程思想泛型编程思想 我们会在接下来学习是当今相对更新的C++11,而非学校教学的C++98 (不排除学校改教C++11的可能性


1. 命名空间 namespace

  • 语法

    namespace spaceName{
      ... 
    } // namespace spaceName
    ...
    namespace spaceName{
      ...  
    } // namespace spaceName

    命名空间可以不连续定义;命名空间内的内容仅在之内起作用,如果在命名空间外部调用,需要添加作用域

    命名空间体 任何种类(包含类、函数定义和嵌套命名空间等)的声明的可能为空的序列

  • 作用域 spaceName::element 表示调用spaceName命名空间下的element

  • 命名空间外实现函数

    namespace ns {
      void function();
    }
    
    void ns::function() {
      ...
    }
  • 声明

    using namespace spaceName;

    在该声明之后可以不添加作用域使用域内元素,如果两个命名空间中出现重名元素,则必须添加作用域以区分(如下)

    using namespace ns1;
    using namespace ns2;
    ns1::a = 10;
    ns2::a = 20;
  • 匿名命名空间

    相当于对命名空间内的内容都加上static关键字

    namespace {
      ...
    //这部分内容让该文件内所有内容可见,但仅仅能让该文件中内容可见,使用时无需作用域操作符前缀
    }
  • 命名空间污染

    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    double max(double v1,double v2)
    {
      cout << "this is my max!" << endl;
    }
    
    int main()
    {
      max(1,1); // output 1
      cout << max(1,1) << endl; 
      return 0;
    }

    这个例子:不会输出this is my max。如果没有using namespace std;就会输出this is my max。因为标准库中的max成为了更好的匹配,所以调用了标准库中的函数

  • 嵌套命名空间

    c++标准委员会于2003年首次提出,最终接受嵌套名称空间的定义如下

    namespace A::B::C {
      ...
    }

    等价于

    namespace A {
      namespace B {
        namespace C {
          ...
        }
      }
    }
  • 内联命名空间

    C++11新增内联命名空间,即在命名空间定义前增加inline关键字,使得该命名空间可以被外层命名空间看到;关键字inline必须出现在命名空间第一次定义的地方,后续再打开命名空间的时候可以写inline,也可以不写。

    这个特性对于代码版本升级且需保留旧版本代码的情况来说实在是太有用了。

    namespace work {
      namespace edition1 {
        ...
          /*初始版本代码*/
      }
      inline namespace edition2 {
        ...
          /*新版本代码*/
      }
    }

    当我们更新代码以及新代码出现故障紧急回撤版本时,只需要增删inline即可,只要保证接口不变,则可以实现无缝更换。

  • typedef&using

    • 使用typedef进行替换,增强类型名可读性 仅仅做个示例,大家不要怕

      std::map<std::string, long> clientLocations;
      typedef std::map<std::string, long> ClientNameToZip;
      ClientNameToZip clientLocations;
    • 使用using精确声明

      using std::cout;
      using std::endl;
      cout << max(1,1) << endl;
    • 在函数体或类内限制using作用范围

      void function(){
        using namespace std;
        cout << "hello world" << endl;
      }
    • typedef能做的事,using也能做,同时结构更清晰

      typedef unsigned int uint_t;
      // equal
      using uint_t = unsigned int;
      typedef std::map<std::string, int> map_int_t;
      // equal
      using map_int_t = std::map<std::string, int>;

总之,我们要少用using做全局声明,宁愿多打一些std::,也不要发生冲突的bug


2. 输入输出流对象 std::cin & std::cout

包含头文件<iostream>

std::cinstd::cout的效率比scanf()printf()低,但是不需要格式控制字符串
*提高效率可以参考CSDN 提高C++的cin/cout效率

  • std::cout

    int a = 10;
    std::cout << a << std::endl; // output: 10
    // equal
    std::cout.operator<<(a).operator<<(std::endl);

    std::endl表示换行

  • std::cin

    int a, b;
    std::cin >> a >> b;

<<>>是重载后的运算符,本质是一个函数(重载运算符会在以后学习到),也可以理解为将内容推入cout,推出cin


3. 字符串 std::string

包含头文件<string>

string是一个容器(后面会学习到);string不用考虑溢出和长度问题,可以直接定义。

std::string str = "hello world";
std::cout << str << std::endl;

std::string类型不同于const char*char[] 类型的字符串,无法通过等号直接赋值;如果使用等号赋值,则需要如下转换

std::string str = "hello";
const char* str2 = str.c_str();

<string>中可以调用的功能

std::string 函数
头文件 #include <string>
声明&初始化 用C风格字符串初始化std::string
std::string str = him;
判空 str.empty();
获取长度 str.size();
str.length();
C风格数组操作 str[0];
尾部插入 str.push_back(ch);
下标pos处插入 str.insert(pos, ch);
子串 下标pos处n个字符:
str.substr(pos, n);
删除 下标from至to处的字符:
str.erase(from, to);
清空 str.clear();
字符串连接 str = str1 + str2 + "";

在之后我们会更加深入地学习string,这里先做个简单了解


4. C++类型转换 static_cast

C++认为C的类型转换没有安全检查,不够安全,于是推出了新的类型转化代替C风格类型转换,采用更严格的语法检查,降低使用风险

C风格类型转换

Type var1 = (Type)expr;

C++新增了四个关键字用 于支持C++风格类型转换

C++风格类型转换

Type var1 = Keyword<Type>(expr);
  • static_cast

    用于内置数据类型的转换

    double d = 1.23;
    long l = d;                           // warning: data loss
    long l2 = (long)d;                    // C
    Long l3 = static_cast<long>(d);       // C++

    用于指针之间的转换

    int i = 10;
    double* pd1 = &i;                     // error: 指针无法隐式转换
    double* pd2 = (double*)&i;            // C
    double* pd3 = static_cast<int*>(&i);  // error: 不支持不同类型指针的转换
    void* pv = &i;                        // C++: Type1* -> void* -> Type2*
    double* pd4 = static_cast<double*>(pv);   
  • reinterpret_cast

    意思是重新解释,能够将一种对象类型转化为另一种,不管他们是否有关系 <Type>(expr)中必须有一个是指针(引用)类型,不能丢掉表达式的constvolatile属性

    1. 改变指针(引用)类型
    int i = 10;
    double* pd = reinterpret_cast<double*>(&i);
    1. 将指针转化为整型变量 将整型变量转化为指针
    void func(void* ptr) {
      int i = reinterpret_cast<int>(ptr); // warning: 8字节的void*缩小到4字节的int
      std::cout << i << std::endl;
    }
    
    int main() {
      int i = 10;
      func(reinterpret_cast<void*>(i));   // warning: 4字节的int扩大到8字节的void*
    }

    将上述的int改为8字节的long long则不会warning

  • const_cast 可以丢掉指针(引用)的constvolatile属性

    int x = 10
    const int* a = &x;
    int* b = a;                     // error
    int* b = (int*)a;               // C   丢掉const限定符
    int* c = const_cast<int*>(a);   // C++ 丢掉const限定符
  • dynamic_cast 用于多态,我们以后会学习到,这里不做赘述


5*. (左值)引用 &

  • 学习该部分之前,请确保你已经学习了C语言的指针的基本用法,如果没有,请参考 Lesson3 指针

理解为:给变量起别名

数据类型 &别名=原名

int a = 10;
int c = 20;

//创建引用
int& b = a; //引用必须初始化 错误:int &b;
std::cout << "a=" << a << std::endl;
std::cout << "b=" << b << std::endl;

b = c; //赋值操作 不是更改引用
std::cout << "a=" << a << std::endl;
std::cout << "b=" << b << std::endl;
//引用初始化后 不可以更改
  • 引用做函数参数

    // 1.值传递
    void swap01(int a, int b) {
        int tmp = a;
        a = b;
        b = tmp;
    
        std::cout << "swap01 a=" << a << std::endl;
        std::cout << "swap01 b=" << b << std::endl;
    }
    
    // 2.地址传递
    void swap02(int* a, int* b) {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    
    // 3.引用传递
    void swap03(int& a, int& b) {
        int tmp = a;
        a = b;
        b = tmp;
    }
    
    int main() {
        int a = 10;
        int b = 20;
    
        swap01(a, b);
        std::cout << "a=" << a << std::endl;
        std::cout << "b=" << b << std::endl;
    
        swap02(&a, &b);
        std::cout << "a=" << a << std::endl;
        std::cout << "b=" << b << std::endl;
    
        swap03(a, b);
        std::cout << "a=" << a << std::endl;
        std::cout << "b=" << b << std::endl;
    }
  • 引用做函数返回值

    • 不要返回局部变量的引用(引用空悬)

      int& test01() {
          int a = 10; //局部变量存放在栈区
          return a;
      }
      
      int main() {
          int& ref = test01();  // 引用空悬
          std::cout << "ref=" << ref << std::endl; //error
      }
    • 函数的调用可以作为左值

      int& test02() {
          static int a = 10;  // 静态变量 存放在全局区
          return a;
      }
      
      int main() {
          int& ref = test02();
          std::cout << "ref=" << ref << std::endl;  // 10
      
          // 如果函数的返回值是引用,这个函数调用可以作为左值
          test02() = 1000;
          std::cout << "ref=" << ref << std::endl;  // 1000
      }
  • 常量引用

    void showValue(const int& val) {
        val = 1000; // error
        std::cout << "val=" << val << std::endl;
    }
    
    int main() {
        const int& ref = 10; 
        // equal
        int tmp=10;     const int& ref=tmp;
    
        int& ref = 10; //error:非常量引用的初始值必须为左值
        ref = 20; //error:表达式必须是可修改的左值
    
        int a = 100;
        showValue(a);
    }

edit Serein
audit NKID00