C++ 入门

/* C++ 入门 */

//《Effective C++》《C++ Primer》《STL 源码剖析》

#include <stdio.h>
#include <stdlib.h>
#include <iostream> // 输入输出流
#include <vector>
#include <string>
#include "../test/lesson/test01/test01.h"

#define N 10
#define ADD(a, b) ((a) + (b))

/* void Func1(int a = 1, int b = 2); */

typedef struct SeqList
{
    int a[10];
    int size;
    int capacity;
} SeqList;

// 升级成了类
struct SeqList1
{
    // 成员函数
    int& at(int i)
    {
        return a[i];
    }

    int& operator[](int i) // 运算符重载
    {
        return a[i];
    }

    // 成员变量
    int a[N];
};

// C 的接口设计
/*
int SLAT(SeqList *ps, int i)
{
    return ps -> a[i];
}

void SLModify(SeqList *ps, int i, int x)
{
    ps -> a[i] = x;
}
*/

// C++ 接口设计
int& SLAT(SeqList& ps, int i)
{
    return (ps.a[i]);
}

typedef struct ListNode
{
    int data;
    struct ListNode *next;
} ListNode;

void PushBack(ListNode *& pHead, int x)
{
    ListNode *newNode = (ListNode *)malloc(sizeof(ListNode));
    newNode -> next = NULL;
    newNode -> data = x;
    if (pHead == NULL)
    {
        pHead = newNode;
    }
    else
    {
        while (pHead -> next != NULL)
        {
            pHead = pHead -> next;
        }
        pHead -> next = newNode;
    }
}

namespace wjl   // 自建命名空间
{
    // 命名空间中可以定义变量/函数/类型
    int rand = 0;

    int Add(int left, int right)
    {
        return left + right;
    }

    int Add(int a, int b, int c)
    {
        return a + b + c;
    }

    struct Node
    {
        struct Node *next;
        int val;
    };

    typedef struct Stack
    {
        int *a;
        int top;
        int capacity;
    } ST;

    void StackInit(ST *ps, int n = 4)
    {
        ps -> a = (int *)malloc(sizeof(n));// 半缺省参数:不给值默认开 4 个
        ps -> top = 0;
        ps -> capacity = 0;
    }

    void StackPush(ST *ps, int x)
    {
        // ...
    }

    namespace xxx // 套娃
    {
        int rand = 1;
    }
}

using namespace std;// 这一行是干什么的?
// C 语言面临命名冲突的问题,比如引用的库文件和自己命名的变量名一致
// using namespace xxx -> 全部展开命名空间,以后就不用使用 xxx 的域作用限定了。
// 但是可能会造成变量名不明确的问题,大型的项目不要全部展开
// std 是 C++ 标准库的命名空间

using wjl::Add;// 部分展开,默认使用 wjl 命名空间下的 Add

/*
int Add(int left, int right)
{
    return (left + right) * 10;
}
*/
// 部分展开会和默认空间内的同名函数产生冲突,所以有时不能部分展开

// 全缺省参数
void Func1(int a = 1, int b = 2) // a = 1, b = 2
{
    cout << a + b << endl;
}

// 半缺省参数 - 必须从右往左给缺省值
void Func2(int a, int b = 20, int c = 30)
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
}

int Add0(int a, int b)
{
    cout << "int Add0(int a, int b)" << endl;

    return a + b;
}

int Add0(int a)
{
    cout << "int Add0(int a)" << endl;

    return a;
}

double Add0(double a, double b)
{
    cout << "double Add0(double a, double b)" << endl;

    return a + b;
}

void Add1(char a, int b)
{
    cout << "void Add1(char a, int b)" << endl;
}

void Add1(int b, char a)
{
    cout << "void Add1(char a, int b)" << endl;
}

// 构成重载,调用存在歧义
void Add2(int a)
{
    cout << "void Add2(int a)" << endl;
}

void Add2(int a, int b = 1)
{
    cout << "void Add2(int a, int b = 1)" << endl;
}

void my_swap(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

int Count1()
{
    int n1 = 0;
    n1++;

    return n1;
}

int& Count2()
{
    int n = 0;
    n++;

    return n;
}

int func()
{
    int a = 1;
    return a;
}

inline int add(int x, int y)
{
    return x + y;
}

void fu(int)
{
    cout << "void fu(int)" << endl;
}

void fu(int *)
{
    cout << "void fu(int *)" << endl;
}

int main()
{
    printf("hello world\n");
    printf("%d\n", wjl::rand);// 域作用限定符 :: 指定命名空间
    // 只会到命名空间中找变量
    printf("%d\n", wjl::Add(1, 2));// 默认情况下只在全局查找,不会在命名空间中查找
    printf("%d\n", Add(1, 2));
    printf("%d\n", wjl::xxx::rand);

    struct wjl::Node node;// 在 Node 前面加命名空间
    // 命名空间会自动合并,在不同的文件中可以定义相同的命名空间

    {
        // << 流插入运算符 -> 输出
        cout << "hello world" << endl;// endl 换行符

        // >> 流提取操作符 -> 输入
        int i = 0;
        cin >> i;

        // 好处:可以自动识别类型
        int k;
        double j;
        cin >> k >> j;
        cout << k << endl;
        cout << j << endl;
        cout << &j << endl;
        printf("%.1lf\n", j);// 控制位数
    }

    // 缺省参数调用 - 备胎
    {
        Func1(2, 3);// 如果传参,那么以传递的参数为准
        Func1(3);// 传一个参数 - a
        Func1();// 如果不传参,那么以函数的形参值为准
        // 显式传参,必须从左往右连续传参,不能直接传后面的参数

        Func2(2);
        wjl::ST st1;
        wjl::StackInit(&st1, 10);// 知道给多少
        for (size_t i = 0; i < 10; i++) {
            wjl::StackPush(&st1, i);
        }

        wjl::ST st2;
        wjl::StackInit(&st2, 100);
        for (size_t i = 0; i < 100; i++) {
            wjl::StackPush(&st2, i);
        }

        wjl::ST st3;
        wjl::StackInit(&st3);// 不知道给多少

        // 缺省参数不能在声明和定义中同时给出,不然会报错 - 重定义
        // 声明给定义不给
    }

    {
        // 函数的重载
        // C 语言不允许同名函数
        // C++ 可以,但是要求构成函数重载
        // 要求函数名相同,参数不同 - 类型不同,个数不同,顺序不同

        cout << Add0(1, 2) << endl;
        cout << Add0(1.1, 2.2) << endl;
        cout << Add0(0) << endl;
        // 会自动调用相应的函数(自动识别类型)

        Add1('c', 2);
        Add1(1, 'c');
        // 顺序不同 - 参数的类型顺序不同
        // 返回值不同,不能构成重载
        // 不在同一个命名空间(域)中,不构成重载

        Add2(1, 2);
        /* Add2(1); */ // error - 调用歧义

        // 那么编译器是如何判断出要使用哪个函数的呢? -  函数名修饰规则
        // 在符号表中,两个重载的函数名被加上不同的前后缀,就可以正常调用了
        // 前后缀的名字与参数的个数,顺序,类型有关

        // 函数名修饰规则带入返回值,那么返回值不同能不能构成重载?
        // 不能,因为调用的时候不知道调用哪个函数
    }

    {
        // 引用 - 给一块空间(变量)取一个别名
        int a = 0;
        int &b = a;// & 在类型和变量的中间

        cout << &a << endl;
        cout << &b << endl;// 取地址
        // 都是 0x7ffffcc24 - 引用变量和它引用的变量共享同一块内存空间

        a++;
        b++;
        cout << a << endl;// 2
        cout << b << endl;// 2

        // 应用场景:引用传参 - 函数的参数是引用变量
        // 好处:形参是实参的别名,共用同一块内存空间,不需要取地址和解引用
        // 举例:swap 函数
        int c = 5;
        my_swap(a, c);
        cout << a << endl;// 5
        cout << c << endl;// 2
        // 链表
        ListNode *head = NULL;
        PushBack(head, 1);
        PushBack(head, 2);
        PushBack(head, 3);
        PushBack(head, 4);

        // 引用的特性
        // 1.必须在定义的地方初始化
        /* int& b; */ // err
        // 2.一个变量可以有多个引用,但是引用在初始化之后不能改变对象
        int &d = a;
        int &e = a;
        int &dd = d;// 给外号取外号

        int x = 1;
        d = x;// 赋值,而不是改变指向
        // 3.对函数使用引用传参时,临时变量只能传给常引用

        // 引用做返回值
        int ret1 = Count1();// 传值返回:n 给临时变量(寄存器),临时变量给 ret
        /* int ret2 = Count2(); */ // 传引用返回 - 临时变量会出现段错误
        /* int& ret2 = Count2(); */ // err
        // 如果出了函数作用域,则不能使用传引用返回
        // 传引用传参的作用:1.提高效率  2.输出型参数(形参的修改影响实参)
        // 传引用返回(出了函数作用于对象还在才可以用):1.提高效率  2.修改返回对象
        SeqList s;
        s.size = 3;
        SLAT(s, 0) = 10;// 如果传的不是引用,那么传递的就是临时变量,具有常性,不可更改
        SLAT(s, 1) = 20;
        SLAT(s, 2) = 30;// 返回引用可以直接赋值修改
        cout << SLAT(s, 0) << endl;
        cout << SLAT(s, 1) << endl;
        cout << SLAT(s, 2) << endl;// 也可以直接打印

        // 常引用
        const int a0 = 0;
        /* int &b0 = a0; */ // err, 权限的放大
        // 权限可以平移,可以缩小,不能放大
        const int &c0 = a0;// 权限的平移

        int x0 = 0;
        const int &y = x0;// 权限的缩小

        int b0 = a0;// 赋值拷贝,b 修改不影响 a,可以

        int i = 0;
        /* double &d0 = i; */ // err
        const double &d0 = i;
        // 类型转换/返回值会产生临时变量,临时变量具有常性 - 权限放大

        int ret = func();
        /* int &ret0 = func(); */ // err
        const int &ret0 = func();// 权限平移才可以

        // 指针和引用的区别
        // 在底层的汇编代码没有区别,说明在底层没有引用,只有指针
        // 引用 - 化了妆的指针
        // 不同点:没有多级引用,没有 NULL 引用,...
    }

    {
        // 类
        SeqList1 s1;
        for (size_t i = 0; i < N; i++)
        {
            // s1.at(i) = i;
            s1[i] = i;
            // s1.operator[](i) = i;
        }

        for (size_t i = 0; i < N; i++)
        {
            cout << s1[i] << " ";
        }
        cout << endl;
    }

    {
        // 内联函数 - 针对于宏函数
        // 宏的优点:直接被替换,没有类型的严格限制,对于调用较多的函数来说不需要频繁的建立栈帧,节省时间
        // 缺点:宏函数容易写错,语法坑很多,不能调试,没有类型安全的检查
        cout << ADD(1, 2) << endl;

        // 内联函数 inline 会在 release 优化的情况下在调用的地方直接展开,而不会再建立栈帧
        cout << add(1, 2) << endl;

        // 内联函数在函数较长或者递归的情况下不会被展开 - 编译器可以忽略内联请求
        // 防止代码膨胀
        // 内联函数不能声明和定义分离(在两个文件中),否则会出现链接错误
        f(1);// 这里调用的时候本来应该出现链接错误,但是 CLion 比较智能
        // 因为在编译阶段,内联函数不会进入符号表中(因为会被直接展开),没有地址
        fx();// 调用的 fx 函数,调用 f 的时候不用走符号表,不需要链接,在原地直接展开

        // 解决方案:内联函数不要声明和定义分离 - 直接在 .h 文件中 inline
        // 因为内联函数不进入符号表,因此不会出现重复定义
    }

    {
        // auto 关键字 - 自动推导
        int a = 0;
        auto b = a;// b 也是 int
        auto c = &a;
        auto &d = a;

        // 普通场景没有什么价值
        // 类型很长,就有价值 - 简化代码
        vector<string> v;
        // vector<string>::iterator it = v.begin();
        auto it = v.begin();

        // auto 不能做函数的参数,也不能做返回值,也不能定义数组
        // 查看参数的实际类型:typeid().name()
        cout << typeid(it).name() << endl;
        cout << typeid(b).name() << endl;
        cout << typeid(c).name() << endl;
    }

    {
        // 范围 for
        int arr[] = {1, 2, 3, 4, 5};

        for (auto x: arr)
        {
            x *= 2;// x 的改变不会印象数组的值
        }

        for (auto e: arr)
            // 依次取数组中的数据赋值给 e
            // 自动判断结束,自动迭代(++)
        {
            cout << e << " ";
        }

        for (auto &x: arr)
        {
            x *= 2;// x 是引用,可以直接改
        }

        for (auto x: arr)
        {
            cout << x << " ";
        }
        cout << endl;
        // 范围 for 必须用数组名(函数形参不行)
    }

    {
        // nullptr 空指针 - 解决 NULL 的二义性
        // 形参可以只写类型

        fu(0);
        /* fu(NULL); */ // err 不知道调用哪个 - NULL 具有二义性
        fu((int *)NULL);

        fu(nullptr);// 新关键字
    }

    return 0;
}





















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值