跳转至

c++的奇怪知识点

1 语言标准

1.1 函数和引用最顶层不能用cv限定

首先看下面示例代码

typedef void F();
typedef int& rint;
typedef int* pint;
struct S {
    const int& cri;//=>const int& cri
    const pint pi;//=>const int* pi
    const rint ri;//=>int& ri
    const F f; // =>void f();
};

::: tip const rint riconst F f展开后const被忽略了,这是为何? 因为函数和引用不是对象类型,所以不能被cv限定符限定 ::: ::: tip 为什么const int& criconst却保存下来了? 因为const限定的是引用的对象,即例如int i=2;const int & ir=i;const限定的是不能改变i的数据;const rint ri展开实际等价于int& const ri,这与const int&是完全不同的,就像const int*pint* const p,但引用时不允许写成int& const ri,所以编译器遇到const rint ri这种情况会自动优化,将const去除掉。参考stackoverflow :::

2 type_trait

2.1 why std::is_const_vis false?

看如下示例代码,std::is_const_v<const int*>是 false 但是std::is_const_v<int* const>是 true。

#include <iostream>
#include <type_traits>

int main()
{
    std::cout << std::boolalpha
        << std::is_const_v<int> << '\n' // false
        << std::is_const_v<const int> << '\n' // true
        << std::is_const_v<const int*> /*false*/
        << " because the pointer itself can be changed but not the int pointed at\n"
        << std::is_const_v<int* const> /*true*/
        << " because the pointer itself can't be changed but the int pointed at can\n"
        << std::is_const_v<const int&> << '\n' // false
        << std::is_const_v<std::remove_reference_t<const int&>> << '\n' // true
        ;
}

解释这种情况要从is_const_v源码中找寻,源码如下:

template <class>
_INLINE_VAR constexpr bool is_const_v = false; // determine whether type argument is const qualified

template <class _Ty>
_INLINE_VAR constexpr bool is_const_v<const _Ty> = true;

源码中is_const_v<const _Ty>,const是用来限定_Ty的,在const int* p_Ty等价于p,const限定的却是p所指向的值。所以std::is_const_v<const int*>是false。再来看看is_const_v<int* const>是true,这个const限定的才是int指针。 指针与const的关系分析在这

3 函数

3.1 函数中定义类或结构

//1.函数中定义类或结构
void func() {
    struct MyStruct
    {
        int a;
    };
    MyStruct s{2};
    cout <<"s.a=" <<s.a << endl;
}
int main()
{
    func();//s.a=2
}

3.2 函数类型、函数指针

  • 写法:函数类型是由2部分组成,函数返回值类型和参数类型。(将函数声明式的函数名和参数名去掉就是函数类型的写法)
int func(int, int) {
    return 0;
}
int main()
{  
    cout<< typeid(func).name();//输出结果 int __cdecl(int,int) ,这就是函数的类型,其中__cdecl是一种函数声明方式,将影响函数名符号表示
    //decltype(func) objFunc=func;//error,func是一个函数指针,不能给一个函数对象
    decltype(func)* pFunc = func;

    using pFunc1 = int(*)(int, int);
    using objFunc1 = int(int, int);

    typedef int objFunc2(int, int);
    typedef int(*pFunc2)(int, int);
}

3.3 函数引用

  • 函数引用和函数指针类似,函数引用需要声明时初始化(和引用变量一样)
int fun(int i)
{
    return i;
}
int main()
{
    int(&pFun)(int)=fun;
    pFun(2);//==2
}

3.4 函数只声明不定义可以使用吗

一般情况下函数只声明不定义时函数是不能用的,但当处于不求值语境下,是可以使用的。例如,decltype推导数据类型时,是不需要计算表达式的值的。

template<class T>
T Func() {
    cout << "done" << endl;
    return T();
}
template<class T>
T Func1();
int main()
{
    decltype(Func<int>()) a=2;
    decltype(Func1<int>()) a1 = 2;//正确,因为decltype不需要执行表达式,因此函数是否定义无所谓
    //Func1<int>();//编译不过,因为没有定义,无法执行  
}

4 类

4.1 匿名类

  • 匿名类就是忽略类名,在定义类时直接定义类实例。
  • 使用场景:设计的类只适用当前作用域,且不想在其它地方被实例化。
  • 示例
#include <iostream>
using namespace std;
int main()
{
    class
    {
    public:
        void fun()
        {
            cout << "hello world" << endl;
        }
    } x;
    x.fun();
}

4.2 强制类型转换如何实现

在类中实现转换函数,形如operator Type ();

class A
{
public:
    A() :m_a(0) {}
    A(const int& a) { m_a = a; }
    //用户定义转换函数 int转换函数,当指定了explicit时,转换需显式转换
    explicit operator int() {
        return this->m_a;
    }
    //or
    //operator int& () {
    //    return this->m_a;
    //}
    operator int* () {
        return &this->m_a;
    }
    operator bool() {
        if (m_a > 0)
            return true; 
        else 
            return false; 
    }
private:
    int m_a;
};
int main()
{
    A a(2);
    bool b = a;//隐式调用 operator bool 
    if (a) {}//if 块中默认bool,故也是显式调用operator bool
    //int转换函数
    int i = (int)(a);//显式调用 operator int 
    int i1 = a;//由于未显式指定int,隐式调用 operator bool 
    i = static_cast<int>(a);//显式调用 operator int 
    int* pi = (int*)(a);//显式调用 operator int* 
}

4.3 类成员指针A::*的使用

  • 语法分析
  • 成员变量指针T A::*
    • A::*:指类型A的成员变量指针或成员函数
    • T是A类中成员变量类型,如char* A::* pclav=&A::m_pa,表示pclav是类A中char*类型的指针,且当前被A::m_a的指针赋值
  • .*->*使用类成员指针指向的成员
    • pa->*pclava.*pclav等价于pa->m_paa.m_pa,其中pa是A指针实例,a是类型对象实例
  • 成员函数指针typedef Ret (A::* pFun)(Args);
    • Ret是函数返回值类型
    • Args是函数参数类型
  • 功能:用来重定向某一类成员变量或成员函数
  • 示例
class A {
public:
    A() :a1(1), a2(2), a3(3) {
        pa1 = new int(5);
        pa2 = new int(6);
    }
    ~A() {
        if (pa1 != nullptr) {
            delete pa1;
            pa1 = nullptr;
        }
        if (pa2 != nullptr) {
            delete pa2;
            pa2 = nullptr;
        }
    }
    int a1;
    int a2;
    int a3;

    int* pa1;
    int* pa2;
};


class B {
public:
    int b;
};
template <class T>
void fun1(int T::*) {
    cout << "";
}
int main() {
    //1. 类成员变量指针
    fun1(&A::a2);
    fun1<A>(0);
    fun1<A>(NULL);
    fun1<A>(nullptr);

    int A::* p1 = &A::a1;
    int A::* p2 = &A::a2;
    int A::* p3 = &A::a3;
    A* pa = new A;
    pa->*p1 = 5;
    pa->*p2 = 6;
    pa->*p3 = 7;

    int* A::* pp1 = &A::pa1;
    delete pa->pa1;
    pa->*pp1 = new int(3);
    //2. 类成员函数指针
    typedef int(A::* pFun)();
    pFun fun = &A::sum;
    (pa->*fun)();
    A a;
    (a.*fun)();
}

4.4 成员函数引用限定

  • 功能:限定成员函数只能从左值或右值调用
  • 示例
#include <iostream>
struct Test {
    //引用限定只能用在非静态成员函数中
    //static void TestSRef()& {
    //    std::cout << " work only if object was a lvalue\n";
    //}
    void TestLRef()& {
        std::cout << "左值"<<std::endl;
    }
    void TestRRef()&& {
        std::cout << " work only if object was a rvalue\n";
    }
};

int main() {
    Test t;
    t.TestLRef();     //ok
    //t.TestRRef();     //不能编译

    //Test{}.TestLRef();//不能编译
    Test{}.TestRRef();//ok
}

4.5 类成员引用变量

  • 规则:
    • 必须要定义有参构造函数,且不能使用默认构造函数
    • 非const引用,构造函数的参数需要时引用或指针类型
    • 初始化引用成员,只能在列表初始化中执行
  • 示例
struct A
{
    A() = default;     // OK
    A(int v1,int& v2) : crv(v1),rv(v2) {} // OK
    const int& crv = 42; // OK
    int& rv;
};
 
A a1;    // 错误:临时量到引用的非良构绑定
int v1=2
A a2(1,v1); // OK(忽略默认成员初始化器)然而 a2.v1 是悬垂引用

4.6 类中巧用using

4.6.1 改变基类成员私用性

class Base {
protected:
    void print(double d) {
        printf("Base");
    }
    double d;
};
class Derived : public Base{
public:
//使继承Base中保护成员并在Derived中成公有成员
    using Base::print;
    using Base::d;
    void print() {
        printf("Derived");
    }
};
int main() {
    Derived d;
    //通过Derived直接访问
    d.print(1.1);//success
    d.d;//success
    //以下编译错误,通过基类作用域符访问不了基类保护成员
    d.Base::print(1.1);//error
    d.Base::d;//error
}

4.6.2 继承基类构造

class Base {
public:
    Base() :m_i(1), m_d(1.1) {}
    Base(int i, double d):m_i(i),m_d(d) {}
private:
    int m_i;
    double m_d;
};
class Derived : public Base {
public:
//声明使用Base中构造函数
    using Base::Base;
};
int main() {
    //调用Base::Base(1,1.1),如果Derived没有声明using Base::Base;将会编译报错
    Derived d(1,1.1);

}

4.7 位域

  • 位域是一种使用bit位的技术,可以压缩数据大小,用于结构体或类中
  • 语法形式:类型说明符 位域名:位域长度
  • 示例:
#include"stdio.h"
#include"string.h"
// 实际占用32个bit(占用一个32位int型),但数据只填充了前面15个bit
struct  bit_struct
{
    int  bit1:3;//占3bit,bit0-bit2

    int  bit2:5;//占5bit,bit3-bit7

    int  bit3:7;//占7bit,bit8-bit14

};

int main()
{
    bit_struct val;
    memset(&val,0,sizeof(val));
    val.bit1=3;
    val.bit2=15;
    val.bit3=63;
    printf("sizeof bit_struct [%d] val.bit1=[%x] val.bit2 [%x] val.bit3 [%x] val [%x]",sizeof(bit_struct),val.bit1,val.bit2,val.bit3,*(int*)(&val));
}
  • 输出结果:
sizeof bit_struct [4] val.bit1=[3] val.bit2 [f] val.bit3 [3f] val [3f7b]

5 匿名联合体union

  • 语法形式:union { 成员说明 };
  • 功能:匿名联合体的成员被注入到其外围作用域中
  • 注意事项:
  • 联合体内成员名不能与外层作用域中其它声明的名称相同
  • 它们不能有成员函数,不能有静态数据成员,且所有数据成员必须为公开。
  • 仅允许非静态数据成员和 static_assert 声明
  • 命名空间作用域的匿名联合体必须声明为 static,除非它们出现于无名命名空间
  • 全局匿名联合体必须声明为 static
  • 示例
namespace nm {
class A 
{
public:
    union
    {
        int i;
        double d;
    };
};
static union
{
    float f=2.3;
};
}
static union
{
    int i1;
};
}
int main() {

    nm::A a;
    a.i = 2;
    nm::f = 1.2;
    i1=3;
}

6 匿名命名空间

  • 匿名命名空间也称无名命名空间,作用类似于全局static,将命名空间内的数据限定在本文件内,外部文件无法通过extern链接
  • 示例
//test3.cpp
static void bar(){}

namespace //匿名的命名空间
{
    float bar2;
    int foo;
}

//test4.cpp
extern int foo;
extern void bar();
extern float bar2; 
int main()
{
bar();                    //外部的bar()被声明为static,这里链接不到符号.不能访问
bar2 = 0.1f;          //外部的匿名空间哩,这里也不能访问.
foo = 0xFF;
return 0;
};//如果将test4的目标和test3的目标进行链接,实际上是找不到这些符号的.链接会失败.

7 布置new(Placement new)

  • 语法形式:new (address) type (initializer)
  • 功能:使用预先分配的内存空间去分配给待分配对象
  • 注意事项:保证预先分配的内存大小大于待分配对象所需内存大小
  • 示例
#include<iostream> 
using namespace std;
int main()
{
    // initial value of X 
    int X = 10;

    cout << "Before placement new :" << endl;
    cout << "X : " << X << endl;
    cout << "&X : " << &X << endl;

    // 将X的内存分配给mem,即mem==&X 
    int* mem = new (&X) int{ 100 };

    cout << "\nAfter placement new :" << endl;
    cout << "X : " << X << endl;
    cout << "mem : " << mem << endl;
    cout << "&X : " << &X << endl;

    return 0;
}
  • 输出结果
Before placement new :
X : 10
&X : 010FF8C8

After placement new :
X : 100
mem : 010FF8C8
&X : 010FF8C8

8 *&的使用

  • *&:指针的引用,功能和作用与**指针的指针类型相似
  • 解释:*&指针的引用,是一个特殊的引用,常见的引用是直接指向对象,而这个引用是指向指针,具有引用所有功能,因此在一定场合可以替代指针的指针**使用。
  • 示例
void pass_by_point(int* p)
{
    //Allocate memory for int and store the address in p
    p = new int;
}
void pass_by_point_point(int** p)
{
    //Allocate memory for int and store the address in p
    *p = new int;
}
void pass_by_point_reference(int*& p)
{
    p = new int;
}

int main()
{
    int* p1 = NULL;
    int* p2 = NULL;
    int* p3 = NULL;

    pass_by_point(p1); //p1 is null
    pass_by_point_point(&p2); 
    pass_by_point_reference(p3); 

    return 0;
}

9 extern数组

  • 声明:char g_arr[1024];
  • 外部声明:extern char g_arr[];