既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
下面是一个以`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、清零和取反](https://bbs.csdn.net/topics/618668825)
#### 问题
如题,如何对一个位(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 怎么用](https://bbs.csdn.net/topics/618668825)
#### 问题
下面这些类型转换的正确用法和应用场景是什么?
* `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(m); // 宽转换,没有信息丢失
char ch = static_cast(m); // 窄转换,可能会丢失信息
int p1 = static_cast<int>(malloc(10 * sizeof(int))); // 将 void 指针转换为具体类型指针
void p2 = static_cast<void>(p1); // 将具体类型指针,转换为 void 指针
double real= static_cast©; // 调用类型转换函数
**`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;
pA = 10; // 编译错误,不允许修改 pA 指向的对象
int pX = const_cast<int*>(pA); // 去掉 const 属性
*pX = 10 // 成功赋值
注意,如果你要修改的对象实际上是一个常量,这个转换就可能不会生效。
const int a = 5; // 常量
const int* pA = &a;
pA = 10; // 编译错误,不允许修改 pA 指向的对象
int pX = const_cast<int*>(pA); // 去掉 const 属性
*pX = 10 // 是否会真正地修改结果未知,因为对于 a 来说,编译器一般在编译的时候会把它放进常量表中
**`reinterpret_cast`** 是重新解释的意思,顾名思义,reinterpret\_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。
reinterpret\_cast 可以认为是 static\_cast 的一种补充,一些 static\_cast 不能完成的转换,就可以用 reinterpret\_cast 来完成。例如两个具体类型指针之间的转换、int 和指针之间的转换(有些编译器只允许 int 转指针,不允许反过来)。
class A{
public:
A(int a = 0, int b = 0): m_a(a), m_b(b){}
private:
int m_a;
int m_b;
};
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
链图片转存中…(img-dE8sAomQ-1715521572425)]
[外链图片转存中…(img-qutO6FSo-1715521572425)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!