C++

最简单的知识有时间记忆回忆起来却最难.

1.类型转换会生成临时变量

(const std::vector)类型转换将导致生成新的临时变量. 所以先做类型转换,再取其地址或其内部属性地址时危险的.

1.1.signed、unsigned 分别指的是signed int和unsigned int,并非转换成当前类型的无符号类型,至少编译器不会自动帮你扩展成被转换数据的类型。

2.delete null不会coredump

这么多年C++才知道,当然学校时是连什么是coredump都不知道地

删除空指针是安全的(因为它什么也没做)。

在定义指针变量时初始化为要么指向有效的内存,要么就指向空,那在你的该变量要失效时就可以只用简单地 delete 掉他们,而不用担心他们是不是被new过。

3.返回值作用域扩展

string GetString(){}

const char *pTemp = GetString().c_str();

printf("%s", pTemp)

const string& temp = GetString();

printf("%s", temp.c_str())



上面两段区别就是:扩展了返回值作用域,第二种将返回值的作用域扩展到了当前函数,第一种返回值作用域仅仅在当前行.

4.C++三个特性

封装/继承/多态

5.dynamic_cast 在进行类型动态转换时不会Core掉的,也就是可以不判空,直接转换之后再判,当然转换失败倘若原来指针没释放会有内存泄露的危险.

dynamic_cast 性能比较低,需要注意使用

6.虚继承

7.volatile

①volatile的本意是“易变的”,作为一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

volatile 相当于告诉编译器, 由它声明的东西的易变的, 不确定的, 可能由外部程序 (如中断程序) 改变的,禁止编译器对其读写或其他操作进行优化。

当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被寄存。

如果定义: int i;

则编译器可能会将其优化, 放到 CPU 寄存器中, 后续访问就可以不用再读内存了,在多数情况下是好的,而有些情况下,比如i被多线程访问到 我们会要求一些变量必须从内存中读取 (如驱动程序, 中断处理程序等等), 这时编译器这个优化就是引起问题。

为避免这种情况, 应该这样定义: volatile int i;

volatile变量的几个例子:

1). 并行设备的硬件寄存器(如:状态寄存器)

2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3). 多线程应用中被几个任务共享的变量

我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile的重要性:

1). 一个参数既可以是const还可以是volatile吗?解释为什么。

2). 一个指针可以是volatile 吗?解释为什么。

3). 下面的函数有什么错误:

int square(volatile int ptr)

{

return
ptr * ptr;

}

答案:

1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

3). 这段代码的有个恶作剧。这段代码的目的是用来返指针
ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int
ptr)

{

int a,b;

a = ptr;

b =
ptr;

return a * b;

}

由于ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int
ptr)

{

int a;

a = *ptr;

return a * a;

}



②volatile 通常也用来阻止编译器具优化操作, 如你有一个非精确延时函数:

void delay(unsigned int timeout)     
{     
    unsigned int i;     
    for (i = 0; i < timeout; i++);     
}

有些编译会足够聪明地注意到这个函数本质上是什么也没干, 会将针对这个函数的调用优化掉, 但这样是不对的, 所以你应该这么声明:

volatile void delay(...)     
{     
    // 同上     
}

③问题场景应用(编译器某些状况无法预知,结果编译器优化方式不当)

首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:



#include

void main(int argc,char argv[])

{

int i = 10;

int a = i;

printf("i=%d",a);

//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道

__asm

{

mov dword ptr[ebp-4],20h

}

int b = i;

printf("i=%d",b);

}

然后,在调试版本模式运行程序,输出结果如下:

i = 10

i = 32

然后,在release版本模式运行程序,输出结果如下:

i = 10

i = 10

输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:



#include

void main(int argc,char
argv[])

{

volatile int i = 10;

int a = i;

printf("i=%d",a);

__asm

{

` mov dword ptr[ebp-4],20h

}

int b = i;

printf("i=%d",b);

}

分别在调试版本和release版本运行程序,输出都是:

i = 10

i = 32

这说明这个关键字发挥了它的作用!

7.1.C++11中有如下几种存储类型声明关键字:

auto 该关键字用于两种情况:1. 声明变量时: 根据初始化表达式自动推断变量类型。2. 声明函数作为函数返回值的占位符。

static static变量只初始化一次,除此之外它还有可见性的属性:1. static修饰函数内的“局部”变量时,表明它不需要在进入或离开函数时创建或销毁。且仅在函数内可见。2. static修饰全局变量时,表明该变量仅在当前(声明它的)文件内可见。3. static修饰类的成员变量时,则该变量被该类的所有实例共享。

register 寄存器变量。该变量存储在CPU寄存器中,而不是RAM(栈或堆)中。该变量的最大尺寸等于寄存器的大小。由于是存储于寄存器中,因此不能对该变量进行取地址操作。

extern 引用一个全局变量。当在一个文件中定义了一个全局变量时,就可以在其它文件中使用extern来声明并引用该变量。

mutable 仅适用于类成员变量。以mutable修饰的成员变量可以在const成员函数中修改。参见上一章chan.simple.h中对mutex的使用。

thread_local 线程周期

8. 作用域和链接(extern)之间的关系(之前倒是考虑过,看到这样的描述,赶紧省事儿记下)

作用域是为编译器服务的,而链接是为链接器服务的.编译器用标识符的作用域来确定在文件的给定位置访问标识符是否合法.当编译器把源文件翻译成目标代码时,它会注意到有外部链接的名字,并最终把这些名字存储到目标文件内的一个表中.因此,链接器可以访问到具有外部链接的名字,而内部链接的名字或无连接的名字对链接器而言是不可见的.

变量的默认存储期限,作用域和链接都依赖于变量声明的位置.

(1)在块内部声明的变量具有自动存储期限,块作用域,并且无链接

(2)在程序的最外层(任意块外部)声明的变量具有静态存储期限,文件作用域和外部链接.

可以通过(auto,static,extern和register)来改变变量的性质

(1)static作用于块内部声明的变量时,变量具有静态存储期限(块作用域,无连接)

static作用域块外部变量时,变量是内部链接(静态存储期限,文件作用域)

注意:虽然函数不应该返回指向auto变量的指针,但是函数返回指向static变量的指针是没有错误的.

(2)extern声明中的变量始终具有静态存储期限.不改变变量的作用域

确定其链接具有一定的难度.一般具有外部链接,如果变量在较早的位置声明为static,那么具有内部链接

extern的重要用处是对变量或函数的声明(不进行定义)

一个名字具有块作用域却有外部链接(本身没什么特别,就这个说法比较奇怪)

比如在第一个文件中定义:

int i;

并且i是放在了任意函数外,所以默认情况下具有外部链接。在另一个文件中,函数f需要访问变量i,所以可以把i声明在f内:

void f(void)

{

extern int i;

....

}

在第一个文件中i具有外部作用域,在第二个文件中i具有块作用域。

  1. 哪个无限循环格式更可取,while(1)还是for(;;)

    C程序员传统上喜欢for(;;)的高效性,因为早期的编译器经常强制程序在每次执行while循环体时测试条件1.但是对现代编译器来说,在性能上两种无限循环应该没有差别.只是习惯问题了

10.宏定义都是全局的

前段时间同事分析一编译不过问题,就是一个宏在命名空间A里,竟然影响到一个不在命名空间A里同名常量以致编译不过。自己可能能分析出来也可能不能。

后面原因便是宏是全局的,并不受限于命名空间。

因为宏是在预编译时展开的,作为预编译当时应该是没有命名空间的,所以宏是全局应该很好理解。

11.explicit

explicit 只对构造函数起作用,用来抑制隐式转换。

12.mutable

mutable 也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const成员函数中也可以修改mutable修改的成员变量。

假如类的成员函数不会改变对象的状态,那么这个成员函数一般会声明为const。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰。

13.继承中构造函数调用

即使子类构造函数初始化列表中将基类构造函数的调用写在后面,实际执行时,基类构造函数也会先于初始化列表中其他变量初始化被调用。

class CBase
    {
    public:
        CBase(int i) :
          m_i(i)
          {
              cout<<"my value is:"<<m_i<<std::endl;
          }
    public:
        int m_i;
    };

    class CSpecific : private CBase
    {
    public:
        CSpecific() :
          m_j(1),
          CBase(2){}

    private:
        CBase m_j;
    };

因此,初始化列表中,基类不要使用当前类的任何变量作为初始化参数,该参数会是未知的。

class CSpecific : private CBase
    {
    public:
        CSpecific() :
          m_j(1),
          CBase(m_j.m_i){}

    private:
        CBase m_j;
    };

13.1.派生类初始化列表无法初始化基类变量

因为成员是属于基类的,你可以访问它,但你不能初始化它。 它只能在基类构造过程中被初始化 。

初始化应在分配完内存之后尽快完成,存在集成情况下,初始化是按照基类、子类顺序完成,这样基类成员初始化当然不可以放在子类、孙类。

14.派生类中调用基类的赋值函数

原来派生类中不仅可以调用基类的构造函数,也能调用基类的赋值函数滴

#include <iostream>
using namespace std;

class base
{
public:
    base(){_a=0;}
    base(const base & obj){
        _a=1;
        cout<<"base copy constructor"<<endl;
    }
    base & operator=(const base & obj){
        _a=2;
        cout<<"base operator="<<endl;
    }
    int _a;
};

class derive : public base
{
public:
    derive(){_b=0;}
    derive(const derive & obj):base(obj){
        _b=1;
        cout<<"derive copy constructor"<<endl;
    }
    derive & operator=(const derive & obj){
        base::operator=(obj);
        _b=2;
        cout<<"derive operator="<<endl;
    }
    int _b;
};

int main(int argc, char *argv[])
{
    derive d1;
    derive d3 = d1;
    cout<<d3._a<<" "<<d3._b<<endl;
    d3 = d1;
    cout<<d3._a<<" "<<d3._b<<endl;

    return 0;
}

15.以typedef定义的类型,在其他头文件中声明时要采用相同形式

·对于以typedef定义的类型,在其他头文件中声明时要采用相同形式,即typedef struct _SimpleStruct SimpleStruct; 否则就会报错。

SimpleStruct.h:450: error: conflicting declaration 'typedef struct _SimpleStruct SimpleStruct'

typedef struct _SimpleStruct

{

int a;

} SimpleStruct;

16.C++ 全局多维数组 extern 声明

声明成extern double gg[];编译可以通过,gg[i]会被被识别为double型变量而非指针。

声明成gg[][],会在连接时报错。

extern double gg[][8]; 二维数组声明 后面一定要加列数 否则编译器不知道二维数组是怎么组织的。

17.const对象默认为文件的局部变量

常用的const变量经常直接定义与头文件中,但是没有报重复定义的错误。

c++ primer,上面这么说,const对象默认为文件的局部变量,即为static,这样就可以在头文件中定义而不会出现重复定义的错误了;非const变量默认为extern。要使const变量能够在其他文件中访问,必须显式地指定它为extern。

由上可知const常量在编译期间会由于多个CPP文件中使用而在多次分配空间,最终无需连接到同一地址。

一个进程中我们都默认会认为常量应为固定的相同值。

c++ primer,如果const变量不是用常量表达式初始化,那么它就不应该在头文件中定义,倘若在头文件定义,那该常量将被表达式多次初始化,倘若表达式多次调用返回值不定(如通过函数初始化,而函数不可重入),则在不懂.o文件中会初始化的值不一样。该条建议配合const变量不应进行修改就可以确保上面一条有效。

18.临时变量不能作为非const引用参数。

因为c++编译器的一个关于语义的限制。如果一个参数是以非const引用传入,c++编译器就有理由认为程序员会在函数中修改这个值,并且这个被修改的引用在函数返回后要发挥作用。但如果你把一个临时变量当作非const引用参数传进来,由于临时变量的特殊性,程序员并不能操作临时变量,而且临时变量随时可能被释放掉,所以,一般说来,修改一个临时变量是毫无意义的,据此,c++编译器加入了临时变量不能作为非const引用的这个语义限制,意在限制这个非常规用法的潜在错误。

也可以这么认为,临时变量的引用就是被设定成常量引用的。

19.位域无法以引用方式传递原因

间接原因实际就是18,因为位域做引用就会被转换成同类型临时变量。

源自:http://stackoverflow.com/questions/1604968/what-does-a-colon-in-a-struct-declaration-mean-such-as-1-7-16-or-32

Update: For the language lawyers among us, the 9.6 section of the standard (C++0x, draft n2914) explains this in detail.

A non-const reference shall not be boundto a bit-field(8.5.3).[Note: If the initializer for a reference of type constT& is an lvalue that refers to a bit-field,the reference is bound to a temporary initialized to hold the value of the bit-field;the reference is not bound to the bit-field directly.]

20.vritual

20.1.虚函数

20.1.1.设置虚函数的原则:

1、只有类的成员函数才能声明为虚函数。

2、静态成员函数不能使虚函数,因为它不受限于某个对象。

3、内联函数不能使虚函数。

4、构造函数不能是虚函数。

20.1.2.子类中虚函数的virtual可有可无

virtual修饰符是会被隐式继承的,所以无需子类中写上。
原文链接: https://www.cnblogs.com/dongzhiquan/archive/2012/06/17/cpp.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/52916

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 上午4:18
下一篇 2023年2月9日 上午4:19

相关推荐