5个有意思的stackoverflow问题总结之一

问题

就是提出引用和指针它们之间到底有啥区别呢?

经典回答
  1. 指针可以改变其绑定的变量,也可以不用初始化(不建议这么做,有危险),
int x = 5;
int y = 6;
int *p;
p = &x;
p = &y;
*p = 10;
assert(x == 5);
assert(y == 10);

引用不可以,且必须初始化。

int x = 5;
int y = 6;
int &r = x;

  1. 指针变量有自己的实际地址和所占空间的大小,x86 上一般是 32 位,但是引用是和它绑定的变量共享一个地址。
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);

  1. 指针可以指向指针的指针,指针的指针的指针,甚至更多层的指针,但引用只能有一层。
int x = 0;
int y = 0;
int *p = &x;
int *q = &y;
int **pp = &p;
pp = &q; // *pp = q
**pp = 4;
assert(y == 4);
assert(x == 0);

  1. 指针可以赋为 nullptr,但引用不能初始化为空。当然你也可以使用其他的方法(毕竟奇淫技巧多)来实现。
int *p = nullptr;
int &r = nullptr; // compiling error
int &r = *p;  // likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0

  1. 指针支持算术运算,比如一个指针数组,使用++就可以拿到下一个位置的指针,+4就可以拿到后面的第四个。
  2. 指针需要以*来取值,引用不用。指向结构体或类对象的指针,还可以以->来获取其内部的成员,引用则使用.
  3. 没有“引用数组”这种说法,只有“指针数组”。
  4. 常量引用可以绑定临时对象,也就是右值,指针不行,搞不好会段错误。
const int &x = int(12); // legal C++
int *y = &int(12); // illegal to dereference a temporary.

  1. 引用用于函数的参数和返回值,有的时候会很有用。比如参数const std::string& name,还有单例模式中的引用返回。

注意,C++ 标准并没有明确要求编译器该如何实现引用,但是基本上所有编译器在底层处理上都会把引用当作指针来处理。比如下面是一个引用,

int &ri = i;

如果未被编译器完全优化,那么它的底层实现其实就和指针一样,开辟一段内存,存放 i 的地址。可以参考,

另外附一些可能需要的链接,

第二个、C++ 中的关键字explicit意味着什么

问题

C++ 中的关键字explicit是什么意思?

经典回答

我们知道编译器是允许进行隐式转换(implicit conversion)的,就是说如果类 A 有一个只有一个参数的构造函数,那么是允许从这个参数对象隐式转换为 A 对象的,直接看个例子就明白了,

class Foo
{
public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo) : m_foo (foo)
  {
  }

  int GetFoo () { return m_foo; }

private:
  int m_foo;
};

下面是一个以Foo类型为参数的函数,

void DoBar (Foo foo)
{
  int i = foo.GetFoo();
}

下面是调用构造函数,进行隐式转换的例子,

int main ()
{
  DoBar(42);
}

实参42是一个整型,不是Foo类型的,但是它可以正常调用,这就是因为隐式转换。因为存在Foo (int foo)这个构造函数,所以可以从int隐式转换为Foo。同样的,如果你定义了这样的构造函数Foo (double foo),也是允许从double隐式转化为Foo的。

但是如果你现在在构造函数的前面加个关键字explicit,它的意思就是要告诉编译器,这个隐式转换不会再被允许了,当编译到DoBar(42)的时候就会报错,除非你显示调用,像这样DoBar(Foo(42))

只有当你有一个好的理由允许构造函数隐式转换,不然的话请把它们都声明为explicit,因为隐式转换容易导致错误,而这个错误往往不容易察觉。比如下面这个的例子,

一个类构造函数MyString(int size),它可以创建一个指定长度size的字符串,而你现在有一个函数print(const MyString&),当调用print(3)的时候(其实你是想调用print("3"),因为粗心少敲了双引号),按道理你期望得到的值是3,但是实际上得到的只是一个长度为 3 的字符串而已。

第三个、如何对一个位(bit)置 1、清零和取反

问题

如题,如何对一个位(bit)置 1、清零和取反?

回答
对特定位进行置 1(bit-set)

|操作符来位置 1,

number |= 1UL << n;

这段代码是将number的第n位赋为 1。

注意,如果number的大小大于unsigned long,就需要把1UL换成1ULL

对特定位进行置置 0(bit-clear)

&操作符来清零一个位,

number &= ~(1UL << n);

number的第n位赋为 0。

对特定位进行置置反(bit-toggle)

^操作符来置反一个位(即 0 变 1,1 变 0),

number ^= 1UL << n;

number的第n位置反。

位检查(bit-check)

还补充以下一些位操作

bit = (number >> n) & 1U;

先将number右移 n 位,然后和 1 进行与操作,得到的值赋给变量 bit。如果第 n 位是 1,那么 bit 也会变为 1;如果是 0,bit 也会是 0。

根据另一个变量来置位
number ^= (-x ^ number) & (1UL << n);

如果 x 等于 1,就把 number 的第 n 位置为 1;如果 x 等于 0,就把 number 的第 n 位置为 0。

注意,如果 x 等于其它数(非 0 非 1),上面的式子结果就未知了。当然你可以使用逻辑运算符!来把 x 置 0 或 置1(也就是布尔化)。

unsigned long newbit = !!x;    // Also booleanize to force 0 or 1
number ^= (-newbit ^ number) & (1UL << n);

译注:原文中还有一段没有翻译,因为我不知道怎么翻译,这里原文贴在这里(此处的代码我在 Visual Studio 2017 上报错:一元负运算符应用于无符号类型,结果仍为无符号类型)。

To make this independent of 2’s complement negation behaviour (where -1 has all bits set, unlike on a 1’s complement or sign/magnitude >C++ implementation), use unsigned negation.

number ^= (-(unsigned long)x ^ number) & (1UL << n);

除了上面的方法,你还可以这么做,

number = (number & ~(1UL << n)) | (x << n);

(number & ~(1UL << n))会对第 n 位清零(clear),(x << n)左移 n 位,也就是赋值第 n 位为 0 或 1。同样的,只有 x 等于 0 或者 1 才会生效,如果是其它的数,结果未知。

另外,如果把这些操作封装在宏里,那么用起来就会显得清楚明白和简洁,可以参考 https://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit/263738#263738

第四、static_cast, dynamic_cast, const_cast 和 reinterpret_cast 怎么用

问题

下面这些类型转换的正确用法和应用场景是什么?

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C 语言风格类型转化(type)value
  • 函数式风格类型转换type(value)
经典回答

static_cast 是静态转换的意思,也就是在编译期间转换,转换失败的话会抛出一个编译错误。主要用于,

  1. 基本数据类型之间的转换。如把 int 转换成 char,把 int 转换成 enum。这种转换的安全性需要开发人员来保证。
  2. void 指针转换成目标类型的指针。这种转换的安全性需要开发人员来保证。
  3. 任何类型的表达式转换成 void 类型。
  4. 有转换构造函数或类型转换函数的类与其它类型之间的转换。例如 double 转 Complex(调用转换构造函数)、Complex 转 double(调用类型转换函数)。
  5. 类层次结构中基类和子类之间指针或引用的转换。进行上行转换(即子类的指针或引用转换成基类表示)是安全的,不过一般在进行这样的转化时会省略 static_cast;进行下行转换(即基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的,一般用 dynamic_cast 来替代。
class Complex{
public:
    Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
public:
    operator double() const { return m_real; }  // 类型转换函数
private:
    double m_real;
    double m_imag;
};

int m = 100;
Complex c(12.5, 23.8);
long n = static_cast<long>(m);  // 宽转换,没有信息丢失
char ch = static_cast<char>(m);  // 窄转换,可能会丢失信息
int *p1 = static_cast<int*>(malloc(10 * sizeof(int)));  // 将 void 指针转换为具体类型指针
void *p2 = static_cast<void*>(p1);  // 将具体类型指针,转换为 void 指针
double real= static_cast<double>(c);  // 调用类型转换函数

dynamic_cast 是动态转换,会在运行期借助 RTTI 进行类型转换(这就要求基类必须包含虚函数),主要用于类层次间的下行转换(即基类指针或引用转换成子类表示)。对于指针,如果转换失败将返回 NULL;对于引用,如果转换失败将抛出 std::bad_cast 异常。

class Base { };
class Derived : public Base { };

Base a, *ptr_a;
Derived b, *ptr_b;

ptr_a = dynamic_cast<Base *>(&b);  // 成功
ptr_b = dynamic_cast<Derived *>(&a);  // 失败,因为基类无虚函数

class Base { virtual void dummy() {} };
class Derived : public Base { int a; };

Base *ptr_a = new Derived{};
Base *ptr_b = new Base{};

Derived *ptr_c = nullptr;
Derived *ptr_d = nullptr;

ptr_c = dynamic_cast<Derived *>(ptr_a);  // 成功
ptr_d = dynamic_cast<Derived *>(ptr_b);  // 失败,返回 NULL

// 检查下行转换是否成功
if (ptr_c != nullptr) {
	// ptr_a actually points to a Derived object
}

if (ptr_d != nullptr) {
    // ptr_b actually points to a Derived object
}

const_cast 主要用来修改类型的 const 或 volatile 属性。

int a = 5;
const int* pA = &a;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值