知识1

命名空间:避免命名冲突与污染,定义命名空间需要用到 namespace 关键字,后面跟空间名字,紧接{},{}中即为命名空间成员。

namespace yx
{
    int rand = 10;
}

int main() 
{
    printf("%d\n", yx::rand);
}

访问变量时按此顺序访问“局部域 -> 全局域 -> 展开的命名空间域(using namespace yx) or 指定访问的命名空间域(::)”。

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

(1)加命名空间名称及作用域限定符

int main() 
{
    printf("%d\n", N::a);
    return 0;
}

(2)使用using将命名空间中某个成员引入

using N::b;
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    return 0;
}

(3)使用using namespace 命名空间名称引入

using namespace N;

知识2

C++输入&输出:cout&cin,使用时必须包含 <iostream> 头文件以及按命名空间使用方法使用 std(C++标准库的命名空间名)。cout 和 cin 是全局的流对象,endl 是特殊的C++符号,表示换行输出。<<是流插入运算符,>>是流提取运算符。与 printf 和 scanf 的区别是更方便,可自动识别变量类型。

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

通常不会全部展开 std,会用 std::cout 或 using std::cout 只展开常用的。

知识点3

缺省参数:可以认为是函数传参的默认值,当调用函数需要传参但未给时便用此值。有半缺省和全缺省两种,全缺省是全部参数都给缺省值,半缺省就是只给一部分,参数是从左往右给的,所以半缺省只能缺左边的不然无法匹配。半缺省不能间隔着给。缺省参数不能在函数声明和定义中同时出现,一般给声明。给定义会在编译阶段报错,因为在链接阶段才会将定义的函数链接在一起,若只传部分参数,会被认为参数传的不完整。

void Func(int a = 10, int b = 20, int c = 30)
{
    ......
    return 0;
}//全缺省

void Func(int a, int b = 10, int c = 20)
{
    ......
    return 0;
}//半缺省

知识点4

函数重载:就是可以有重名函数,有个前提是形参列表(参数个数、参数类型或类型循序)不同,其实底层就是将参数加入函数名后面依此区分,返回值不同不构成重载。比如一个函数声明为  void Func(int a, int b),另一个为 void Func(double a, double b),则这俩函数构成重载,在底层代码里其实分别为 Funcii 和Funcdd。

知识点5

引用:给已存在变量取别名,编译器不会为其开辟内存空间,它和它引用的变量共用同一块内存空间。

void TestRef()
{
    int a = 10;
    int& ra = a;

    printf("%d\n", &a);
    printf("%d\n", &ra);
    return 0;
}

引用特性:

(1)引用在定义时必须初始化

(2)一个变量可以有多个引用

(3)引用指向不可变(引用一旦引用一个实体,再不能引用其他实体)

使用场景:

(1)引用做参数:做参数有两个大作用,其一作为输出型参数(就是在函数内部改变,需要在外面拿到,类似想要改变变量就需要传指针,想要改变指针就需要传二级指针,通过引用就可以直接传引用这样就可直接实现),其二可以提高效率(以值作为参数或返回值,函数不会直接传递实参或将变量本身直接返回,而是返回其的一份临时拷贝,因此效率低下,引用则不会生成临时拷贝效率高)

(2)引用做返回值:做返回值同样有两个好处,一个同上可提高效率,另一个为引用做返回值可直接修改和获取返回值。一般情况下都是静态引用返回,如下有五个场景分析。

场景一:此场景没有问题,因为 n 此时在静态区,函数结束后不会被销毁因此没问题。

//传值返回
int Count()
{
    static int n = 0;
    n++;

    //......
    return n;
}

int main()
{
    int ret = Count();

    return 0;
}

场景二:此场景函数结束时 n 会被销毁,此时 ret 是不确定的。

int Count()
{
    int n = 0;
    n++;

    //......
    return n;
}

int main()
{
    int ret = Count();

    return 0;
}

场景三:此时无需生成临时变量且不会被销毁,效率高,无问题。

//传引用返回
int& Count()
{
    static int n = 0;
    n++;

    //......
    return n;
}

int main()
{
    int ret = Count();
    cout << ret <<endl;

    return 0;
}

场景四:这里打印 ret 的值是随机的,如果函数结束,栈帧销毁,没清理栈帧,那么 ret 的值侥幸正确;如果函数结束,栈帧销毁,清理栈帧,那么 ret 的值是随机值。

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

    //......
    return n;
}

int main()
{
    int ret = Count();
    cout << ret << endl;

    return 0;
}

场景五:此时第一个 ret 的值可能是1也可能是随机值,第二个 ret 一定是随机值。

int Count()
{
    int n = 0;
    n++;

    //......
    return n;
}

int main()
{
    int& ret = Count();
    cout << ret << endl;

    printf("SSSSSSSSSSSSSSSSSSS\n");
    cout << ret << endl;

    return 0; 
}

总结:基本所有场景都可用引用传参;谨慎用引用做返回值。出了函数作用域,对象不在了,就不能用引用返回了,还在就可用。

常引用:

int main()
{
    const int a = 0;
    //int& ra = a;    //该语句编译时出错,引用过程中权限不能放大

    const int c = 0;
    int& d = c;    //该语句没问题,c 拷贝给 d ,没有放大权限,因为 d 的改变不影响 c

    int x = 0;
    int& y = x;    //引用过程中,权限可平移
    const int& z = x;0     //引用过程中,权限可缩小
    ++x;    //可以,x 权限未被限制,但 z 不可变,但 x 变 z 也变
    //++z;    //不可

    double dd = 1.11;
    int ii = dd;    //隐式类型转换
    //int& rii = dd;    //不同类型转化会产生临时变量,临时变量又具有常性,因此发生了权限放大
    const int& rii = dd;    //此时可以,权限平移
    
    return 0;
}



int func1()
{
    static int x = 0;
    return x;
}

int& func2()
{
    static int x = 0;
    return x;
}

int main()
{
    //int& ret1 = fun1();    //不可,传值有临时拷贝,发生了权限放大
    const int& ret1 = func1();    //可,权限平移
    int ret1 = func1();    //拷贝
    
    int& ret2 = func2();    //可,权限平移
    const int& ret2 = func2();    //可,权限缩小
}



int main()
{
    int i = 1;
    double j = 1.11;
    if(j > i)    //此时 i 提升,会生成 double 临时变量
    {
        cout << "xxxxx" << endl;
    }
    return 0;
}

引用和指针的区别:

引用语法层面:不开空间,是对其取别名

指针语法层面:开空间,存储其地址

汇编语言都一样,底层一样。

int main()
{
    //引用语法层面:不开空间,是对其取别名
    int& ra = a;
    ra = 20;

    //指针语法层面:开空间,存储其地址
    int* pa = &a;
    *pa = 30;
}

引用和指针的不同点:

(1)引用概念上定义一个变量的别名,指针存储一个变量地址。

(2)引用在定义时必须初始化,指针没有要求。
(3) 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体。
(4)没有NULL引用,但有NULL指针。
(5)在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)。
(6)引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
(7)有多级指针,但是没有多级引用。
(8)访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
(9)引用比指针使用起来相对更安全。 

更多推荐