C++学习_3

一、简介

        Day03,C11入门

二、主要内容

 1. 名字空间域

        在C++中支持三种域:局部域、名字空间域和类域。

        名字空间域是随着标准C++而引入的,它相当于一个更加灵活的文件域(全局域),可以用花括号把文件的一部分括起来,并以关键字namespace开头给它起个名字。

 a. 定义方式

        如下所示,定义名为tmp的名字空间域,

namespace tmp
{
    int g_max = 10;
    int g_min = 0;
    char ch = 'a';
    void fun()
    {
        // 函数体
    }
    int my_add(int a, int b)
    {
        return a + b;
    }
}

        其主要是为了解决全局名字污染问题,即防止程序中全局实体名与其他程序的全局实体名的命名冲突。

 b. 使用方法

        如下所示,

namespace A
{
    int g_max = 10;
    int g_min = 0;
    int my_add(int a, int b)
    {
        return a + b;
    }
}

namespace B
{
    double g_max = 12.23;
    double g_min = 0.0;
    double my_add(int a, int b)
    {
        return a + b;    
    }
}

int main()
{
    int a = A::my_add(1,5);
    int b = A::g_max;
    
    double dx = B::my_add(12.566, 546.15);
    return 0;
}

        在命名空间A和B中,分别定义了my_add()函数,在使用时需通过作用域限定符"::"来强制调用该命名空间的my_add()函数。

        或在程序前使用using namespace 名字空间名来强制程序在该名字空间下执行程序,如下,

int main()
{
    using namespace A;
    cout << g_max << endl;    // 10
    cout << g_min << endl;    // 0
    return 0;
}

        程序执行后会输出10,0。

 c. 注意事项

        若在不同命名空间中定义了相同的函数,且都使用using namespace 名字空间名限定空间,则在程序调用任一函数时会产生二义性,即程序无法判断使用哪一个名字空间的函数,此时需要作用域限定符"::"

 2. new/delete

        C++的动态内存管理关键字,类似于C语言中的malloc、calloc、realloc以及free函数(在stdlib.h库中)。

  a. C中的动态内存管理

        使用stdlib.h库中的malloc、calloc、realloc以及free函数来进行动态内存管理。

int main()
{
    int n = 10;
    // 申请一个整形空间
    int* ip1 = (int*)malloc(sizeof(int));
    // 申请连续的n个整形空间
    int* ip2 = (int*)malloc(sizeof(int) * n);
    // 申请连续的n个空间,并赋值为0
    int* ip3 = (int*)calloc(n, sizeof(int));
    // 将ip2大小扩大到2*n
    ip2 = (int*)realloc(ip2, sizeof(int) * n * 2);
    
    // 释放全部申请的空间
    free(ip1);
    free(ip2);
    free(ip3);
    // 释放后需要将其指向空,否则其会成为失效指针
    ip1 = nullptr;
    ip2 = nullptr;
    ip3 = nullptr;
    return 0;
}

  b. C++中的动态内存管理

        使用关键字new/delete来进行管理,new表示申请空间,delete表示释放空间。

int main()
{
    int n = 10;
    // 申请一个整形空间,并初始化为10
    int *ip1 = new int{ 10 };
    // 申请连续的n个整形空间,不进行初始化
    int *ip2 = new int[n];
    // 申请连续的n个整形空间,并将所有空间初始化(默认为0)
    int *ip3 = new int[n]{ };
    /*在初始化时,所给出的个数需小于等于指定的空间大小*/
    
    // 释放空间
    // 若为一个空间,则如下所示释放即可
    delete ip1;
    // 若为多个空间,则需如下所示释放,表示释放所有空间
    delete[] ip2;
    delete[] ip3;
    // 仍需指向为空,否则为野指针
    ip1 = NULL;
    ip2 = NULL;
    ip3 = NULL;
}

 c. new/delete的函数化使用

        在底层中,operator new实际上调用malloc()函数来申请空间。

int main()
{
    int n = 10;

    // 如下方式类似于 malloc(sizeof(int))
    int* ip1 = (int*)::operator new(sizeof(int));
    // 如下方式类似于 malloc(sizeof(int) * n)
    int* ip2 = (int*)::operator new(sizeof(int) * n);

    // free(ip1)
    ::operator delete(ip1);
    ::operator delete[](ip2);

    //仍需指向为空
    ip1 = NULL;
    ip2 = NULL;
    return 0;
}

 d. 定位new的使用

        用于对申请的空间的初始化

int main()
{
    int n = 10;
    int* ip1 = (int*)malloc(sizeof(int));
    int* ip2 = (int*)::operator new(sizeof(int) * n);
    // ip1、ip2都是仅申请空间,但未初始化
    
    // 对ip1指向的空间进行初始化,值为10
    new(ip1) int { 10 };

    // 此方式会报错,int代表仅初始化一个空间
    // new(ip2) int { 1,2,3,4,5 };    // error
    // 此方式正确,但仅会初始化5个空间,若为int[n],则其他空间会初始化为0(默认值)
    new(ip2) int[] { 1,2,3,4,5 };


    free(ip1);
    ip1 = NULL;
    ::operator delete[](ip2);
    ip2 = NULL;
    return 0;
}

 e. 区别

对于内置类型new / delete / malloc/free可以混用。区别如下,
        1. new/delete 是C++中的运算符。malloc/free是函数。
        2. malloc申请内存空间时,手动计算所需大小,new只需类型名,自动计算大小;
        3. malloc申请的内存空间不会初始化,new可以初始化;
        4. malloc的返回值为void*,接收时必须强转,new不需要;
        5. malloc申请内存空间失败时,返回的是NULL,使用时必须判空;
            new申请内存空间失败时抛出异常,所以要有捕获异常处理程序;

3. C11新特性

 a. 类型推导:auto/decltype

        C++11引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能方便的获取复杂类型,而且还能简化书写,提高编码效率。

1) auto

auto类型推导:auto定义的变量,可以根据初始化的值,在编译时推导出变量名的类型。

int main()
{
    auto x = 10; // 10 -> x = int, auto = int
    auto dx = 12.23; // 默认为双精度即double类型,auto = double
    auto df = 12.23f; // 指定该数据类型为float类型,auto = float
    auto ch = 'a';    // auto = char
}

        复杂情况(指针)的推演情况如下所示,

int main()
{
    auto x = 5; // auto = int
    const auto* xp = &x; // auto = const int
    auto ip = &x;  // auto = int*
    auto* sp = &x; // auto = int
    // *和&都是和变量名结合 并不与关键字结合
    
    const int a = 10;
    auto* p = &a;    //auto = const int
    return 0;
}

       auto总结:

       auto并不能代表一个实际的类型声明,只是一个类型声明的"占位符",在实际使用中,必须给予auto声明的变量以初始值,以让编译器推断出它的实际类型,并在编译中将auto占位符替换为真正的数据类型;

       不能推演数组,即auto ar[10] = { 1,2,3,4 };会报错;

       在结构体中,成员变量不能使用auto来声明;

 2) decltype

 decltype类型推导,用于在编译时推导出一个表达式的类型。

         语法格式:decltype(exp),其中exp表示一个表达式,从格式上来看,decltype很像sizeof——用于推导表达式类型大小的操作符,如下,

int main()
{
    int x = 10;
    decltype(x) y = 1; // y = int
    decltype(x + y) z = 10; // z = int
    double dx = 10;
    decltype(x + dx) c; // c = double

    const int& a = 10;
    decltype(a) j = 10;    // j = const int&

    int m = 10;
    const decltype(m)* p = &m; // p = const int *
    decltype(m)* ip = &m; // ip = int *

    const int n = 10;
    decltype(n) k = n; // k = const int
    reutrn 0;
}

 b. 基于范围的for循环

        在C98中,不同的容器和数组,遍历的方法不尽相同,写法不统一,也不够简洁,而C++11基于范围的for循环以统一、简洁的方式来遍历容器和数组,用起来更方便。

 1) C语言中的for循环       

        在C语言中,遍历数组有如下两种方式,

int main()
{
    int ar[] = { 1,2,5,6,7,8,9,5,3,4 };
    int n = sizeof(ar) / sizeof(ar[0]);
    for(int i = 0; i < n; i++)
        cout << ar[i] << " ";

    int* ip = NULL;
    for(ip = ar; ip != ar + n; ip++)
        cout << *ip << " ";
    return 0;
}
 2) C++中基于范围的for循环

        在C++中,基于范围的for循环的一般格式:

for(ElemType val: array)
{
    // 循环体
}

        ElemType:是范围变量的数据类型。它必须与数组(容器)元素的数据类型一样,或者是数组元素可以自动转换过来的类型;
        val:是范围变量的名称。该变量将在循环迭代期间依次接收数组中的元素值。在第一次循环迭代期间它接收的是第一个元素的值;在第二次循环迭代期间,它接收的是第二个元素的值;以此类推;
        array: 是要让该循环进行处理的数组(容器)的名称。该循环将对数组中的每个元素迭代一次;
        statement: 是在每次循环迭代期间要执行的语句。要在循环中执行更多的语句,则可以使用一组大括号来包围多个语句。与其他循环体一样,可以用continue来结束本次循环,也可以用break来跳出整个循环。

int main()
{
    int ar[] = { 1,2,5,6,7,8,9,5,3,4 };
    // 1
    for(int x : ar)
    {
        cout << x << " ";
        x += 10;    // 不影响数组中的值,因为仅是将数组中的值传递过来
    }
    // 遍历结果 1 2 5 6 7 8 9 5 3 4
    cout << endl;
    // 2
    for(int x : ar)
        cout << x << " ";
    // 遍历结果 1 2 5 6 7 8 9 5 3 4
    cout << endl;
    return 0;
}

        如上所示的代码中,第一个循环和第二个循环的结果都是相同的,对于x += 10;在迭代过程中,仅是将ar中的值传递过来,并不会改变实参的值,类似于函数的形参。但若以如下方式遍历数组,会得到不同的遍历结果。

int main()
{
    int ar[] = { 1,2,5,6,7,8,9,5,3,4 };
    // 1
    for(int& x : ar)
    {
        cout << x << " ";
        x += 10;
    }
    // 遍历结果 1 2 5 6 7 8 9 5 3 4
    cout << endl;
    // 2
    for(int x : ar)
        cout << x << " ";
    // 遍历结果 11 12 15 16 17 18 19 15 13 14
    cout << endl;
    return 0;
}

在上述循环一中,每次迭代将x命名为各空间的别名,即取得了各空间的地址,

因此在x += 10后会影响个空间的值。

        也可使用auto来进行遍历数组,如下所示,

int main()
{
    int ar[] = { 1,2,3,4,5,6,7,8,9 };
    char str[] = { 'a', 'b', 'c', 'd'};
    double dx[] = { 1.2, 2.3, 3.4, 4.5 };
    
    for(const auto& x : ar)
        cout << x << " ";
    cout << endl;
    for(const auto& x : str)
        cout << x << " ";
    cout << endl;
    for(const auto& x : dx)
        cout << x << " ";
    cout << endl;
    return 0;
}

        结合之前所学习的知识点,可得出如下结论,

int main()
{
    int ar[] = { 1,2,3,4,5,6,7,8,9 };
    int* ip = ar;
    int(&br)[] = ar;
    auto &cr = ar;
    
    // 仅cr可通过基于范围的for循环进行遍历,ip和br都不可以
    // ip仅是一个指针,br无法确定数组的大小
    for(auto x : cr)
    cout << x << " ";
    cout << endl;
    return 0;
}

 c. 指针空值——nullptr:"指针空值类型"的常量

        初始化指针是将其指向一个"空"的位置,比如0。但在大多数计算机系统中,并不允许用户程序写地址为0的内存空间,倘若程序无意中对该指针所指地址赋值,通常在运行时就会导致程序退出。虽然程序退出并非什么好事,但这样的错误也是十分明显的,容易进行修改,因此在大多数的代码中,常常使用指针初始化为0或NULL的语法。如下所示为在C/C++中对NULL的定义,

#ifndef NULL
    #ifdef cplusplus
        #define NULL 0             // C++
    #else
        #define NULL ((void*)0)    // C
    #endif
#endif
 1) C语言中的空指针

        由上述可知,在C语言中,对于NULL是将0强转为无类型指针。由于C语言中对指针类型的约束较少,因此将指针指向为NULL是成立的,即

int main()
{
    int a = 10;
    void* vp = &a;
    
    // C编译器会自动将vp强转为int*指针
    int* ip = vp;
    // C编译器会自动将vp强转为char*指针
    char* cp = vp;
    return 0;
}
 2)  C++中的空指针

        由上述可知,在C++中。仅将NULL定义为0值。这是由于C++中对指针类型的约束较强,即

int main()
{
    int a = 10;
    void* vp = &a;
    
    // C++ 不允许该语法
    // int* ip = vp;    // error
    // C++ 不允许该语法
    // char* cp = vp;    // error

    // 仅该种语法成立
    int* ip = (int*)vp;
    char* cp = (char*)vp;
    return 0;
}

        为了解决C++中NULL定义为0的不方便之处,C11标准提出了nullptr,即指针空值常量,其可以赋值给任意类型的指针,且仅能赋值给指针类型变量,不能被修改,可通过强制类型转换赋值给普通变量,可通过下述代码得到更直观的表现。

void fun(int a)
{
    cout << "fun(int a)" <<endl;
}
void fun(char* p)
{
    cout << "fun(char* p)" <<endl;
}
int main()
{
    fun(0);          // fun(int a)
    fun(nullptr);    // fun(char* p)
    return 0;
}

Tips:在未来的C++编写中,只使用nullptr,摒弃NULL的使用。

 d. typedef和using

        typedef和using是给予已有的类型名一个替代名称。 定义方式如下,两者作用都是一样的。       

typedef unsigned char  u_int8;
typedef unsigned short u_int16;

using u_int8  = unsigned char;
using u_int16 = unsigned short;

        但在实际应用中,using更加灵活,最明显的特点是其可以和模板进行联系,如下所示,

template<class _T>
using pointer = _T*;

int main()
{
    int a = 10;
    pointer<int> p = &a;

    char ch = 'a';
    pointer<char> cp = &ch;
    return 0;
}

Tips:在大部分源码中,都是以如上所示的方式定义的。

 e. 左、右、将亡值

        C11中,左值是指拥有地址的变量,右值是指常量(即没有地址),将亡值是指在函数调用中返回的值,其地址已被销毁。

         意义在于可以通过左值/右值引用来进行函数重载,即不同的左值/右值引用也会构成函数重载,即

void fun(int& a)
{
    cout << "left" << endl;
}
void fun(int&& a)
{
    cout << "right" << endl;
}

  f. string字符串(简单使用)

C中的字符串函数库:string.h / cstring。C语言中的字符串操作比较复杂,即操作字符串数组char str[],各种操作函数可参考相关资料。

在C++中,引入了string类型,库函数为string,值得注意的是其仅在std命名空间下使用,即必须使用using namespace std;

 1) 定义方式

         声明及定义方式如下所示,

int main()
{
    // 声明与定义
    string s1{ "test" };
    string s2 = { "hello" }
    string s3 = "world"; 
    string s4 = s1;
    cout << s1 <<endl;

    return 0;
}
 2) 使用方法
// 使用上述定义

int main()
{
    // 组合/加长
    s1 += s2;
    cout << s1 <<endl;

    // 判断是否相等
    if(s1 == s2)
        cout << "=" <<endl;
    else
        cout << "!=" <<endl; 

    // 获取长度
    int len = s1.length();

    // 外部输入
    string s5;
    cin >> s5;
    cout << s5 << endl;
    return 0;
}

        值得注意的是,在进行外部输入时,若输入的字符串中包括空格,则系统仅接收空格之前的字符串,其之后(包括空格)都会被忽略,即

int main()
{
    string s6;
    // 输入:hello world.
    cin >> s6;

    cout << s6 << endl;
    // 输出:hello
    return 0;
}

        这种情况的解决方法为使用全局函数getline(),如下所示,

int main()
{
    string s6;
    // getline()函数以回车(换行符作为输入结束)
    getline(cin, s6);    // 输入hello world
    cout << s6 << endl;  // 输出hello world
    return 0;
}

 Tips:相比与C语言对字符串的操作,C++的操作更为方便和简洁。

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值