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 ri
和const F f
展开后const
被忽略了,这是为何?
因为函数和引用不是对象类型,所以不能被cv限定符限定
:::
::: tip 为什么const int& cri
中const
却保存下来了?
因为const
限定的是引用的对象,即例如int i=2;const int & ir=i;
,const
限定的是不能改变i
的数据;const rint ri
展开实际等价于int& const ri
,这与const int&
是完全不同的,就像const int*p
和int* 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->*pclav
,a.*pclav
等价于pa->m_pa
和a.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[];