码迷,mamicode.com
首页 > 编程语言 > 详细

C++易混淆知识点整理

时间:2016-04-16 22:56:19      阅读:420      评论:0      收藏:0      [点我收藏+]

标签:

// 1 ///////////////////////////////////////////////////////////////////////

// 常量指针:变量不可修改,指针可修改
const int *p;
int const *p;
// 指针常量:指针不可修改,变量可修改
int *const p;
// 指针解引用之前,一定确保已被初始化为一个确定/合适的地址
int *p;     // 分配了指针p的内存,但并没有初始化,指针所指向的数据的内存不知道
// 指向数组的指针:指向一个数组的指针
int *p = new int[5];
int a[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &a;
// 指针数组:数组的元素是地址(指针)
int b1,b2,b3,b4,b5;
int *ptr[5] = {&b1, &b2, &b3, &b4, &b5};
// 指向指针数组的指针
int* (*p)[5] = &ptr;
// 函数指针:函数返回类型是某一类型的指针
int *f(x,y);
// 指针函数指向函数的指针变量,即本质是一个指针变量
int (*f) (int x); /* 声明一个函数指针 */
 f=func; /* 将func函数的首地址赋给指针f */
// 函数指针数组:数组的每一个元素都是一个函数指针,符合函数指针数组所规定的返回值和参数标志
double (*f_attr[])(double, double);
// 指向函数指针数组的指针
double (*(*f_attr)[])(double, double);

// 2 ///////////////////////////////////////////////////////////////////////
临时变量/引用参数和const
--对于一个函数:
(1)如果其接收常规引用参数,其意图在于修改变量,此时C++会禁止创建临时变量;(因为如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现)
(2)如果其接收const引用,其意图在于不修改只使用变量,此时在满足下面两个条件之一的情况下,C++便会为变量创建临时变量:
1. 实参的类型正确,但不是左值;
2. 实参的类型不正确,但可以转换为正确的类型.

--应尽可能使用const引用:
1. 使用const可以避免无意中修改数据的编程错误;
2. 使用const使函数能够处理const和非const实参,否则将只能接受非const数据;
3. 使用const引用使函数能够正确生成并使用临时变量;

--引用非常适合用于结构和类,可以避免大量拷贝导致的资源消耗;同时也可以令返回值为引用,需要注意的是要避免返回函数终止时不再存在的内存单元引用.
--对于返回类型,常规(非引用)返回类型为右值,不能通过地址访问;而引用返回类型为左值,可以被修改和取地址;当然,const引用返回不能被修改.
--临时变量和函数的非引用返回值都是右值,不可取地址.

// 3 ///////////////////////////////////////////////////////////////////////
类成员函数的重载、重写、和覆盖区别
a.成员函数被重载的特征:
 (1)相同的范围(在同一个类中);
 (2)函数名字相同;
 (3)参数不同;
 (4)virtual关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
 (1)不同的范围(分别位于派生类与基类);
 (2)函数名字相同;
 (3)参数相同;
 (4)基类函数必须有virtual关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
 (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

// 4 ///////////////////////////////////////////////////////////////////////
5种变量存储方式
存储描述                     持续性             作用域             链接性             如何声明
--------                         --------          --------             -------            -----------
自动                             自动                代码块             无                 在代码块中
寄存器                         自动                代码块              无               在代码块中,使用关键字register
静态,无链接性              静态                代码块              无               在代码块中,使用关键字static
静态,外部链接性           静态                文件                 外部                不在任何函数内
静态,内部链接性           静态                文件                 内部                不在任何函数内,使用关键字static

// 5 ///////////////////////////////////////////////////////////////////////
切记:声明类只是描述了对象的形式,并没有创建对象.因此,在创建对象前,将没有用于存储值的空间.
所以,对于需要在类中声明常量,可以采用枚举,同时,针对于静态常整型变量,也可以在类声明时直接赋值;
注意:在类中声明枚举并不会创建类数据成员.也就是说,所有对象中都不包含枚举.
同时,C++11提供了新枚举--作用域内枚举.常规枚举会自动转换为整型,但是作用域枚举不能隐式地转换为整型.

// 6 ///////////////////////////////////////////////////////////////////////
类的自动转换和强制类型转换
1. 在C++中,只有接受一个参数的构造函数才能作为转换函数(如果有2个参数,第二个参数提供默认值,同样可以用来作为转换函数).可以通过使用explicit关闭这种特性.
2. C++类的转换函数(将类对象转换成基础类型):
(1)转换函数必须是类方法;
(2)转换函数不能指定返回类型;
(3)转换函数不能有参数;
例如,转换为typeName类型的函数原型如下:
[explicit] operator typeName()
注:typeName指出了要转换成的类型,因此不需要指定返回类型.转换函数是类方法意味着:它需要通过类对象来调用,从而告知函数要转换的值.因此,函数不需要参数.
在C++11 中,可以通过explicit将转换运算符声明为显式.

// 7 ///////////////////////////////////////////////////////////////////////
String类相关函数:

// 1. 一般在cpp文件定义类声明中的static变量,如:

int String::num_strings = 0;

// 2. String类的构造函数:
String::String()
{
    len = 4;
    str = nw char[1];
    str[0] = \0;
    num_strings++;
}
// 3. String类的析构函数:
String::~String()
{
    --num_strings;
    delete [] str;
}
// 4. String类的拷贝构造函数:
String::String(const String &st)
{
    num_strings++;
    len = st.len;
    str = new char[len+1];
    std::strcpy(str, st.str);
}
// 5. String类的赋值运算符
String & String::operator=(const string & st)
{
    if(this == &st)
        return *this;
    delete [] str;
    len = st.len;
    str = new char[len+1];
    std::strcpy(str, st.str);
    return *this;
}
// 6. C_风格字符串赋值运算符
String & String::operator=(const char *s)
{
    delete [] str;
    len = std::strlen(s);
    str = new char[len+1];
    std::strcpy(str, s);
    return *this;    
}

// 8 ///////////////////////////////////////////////////////////////////////
虚函数的工作原理:
通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员.隐藏成员中保存了一个指向函数地址数组的指针.这种数组称为虚函数表(virtual function table,vtbl).虚函数表中存储了为类对象进行声明的虚函数的地址.例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表.派生类对象将包含一个指向独立地址表的指针.如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地址.如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中.注意,无论类中包含的虚函数是1个还是10个,都只需要在对象中添加1个地址成员,只是表的大小不同而已.
注意:
1. 通常应给基类提供一个虚析构函数,即使它并不需要析构函数.因为如果你定义virtual虚函数,意味着使用多态,那么你就要为动态对象占有的内存定义一个虚析构函数.否则的话,当你使用一个指向派生类对象的基类指针进行析构操作的时候,你将仅仅只是释放了基类对象相关数据,而派生类对象依然没能被成功释放.
2. 友元不能是析构函数,因为友元不是类成员,而只有类成员才能是虚函数.
3. 当定义了一个纯虚函数的时候,表明这个类为抽象基类.抽象基类一般是作为其他类的基类,并且不能用来创建对象.

// 9 ///////////////////////////////////////////////////////////////////////
继承和动态内存分配(假设基类使用了动态内存分配)

// Base Class Using DMA
class baseDMA{
    private:
        char *label;
        int rating;
    public:
        baseDMA(const char *l = "null", int r = 0);
        baseDMA(const baseDMA &rs);
        virtual ~baseDMA();
        baseDMA & operator=(const baseDMA & rs);
        ....
}

声明中包含了构造函数使用new是需要的特殊方法:析构函数,拷贝构造函数和重载赋值运算符.
1. 派生类不使用new:那么不需要为派生类定义显式析构函数,拷贝构造函数和赋值运算符.
(1)由于派生类不需要执行任何特殊操作,所以默认析构函数是合适的;
(2)默认拷贝构造函数执行成员复制,这对于动态内存分配来说是不合适的.但对于没有使用new的派生类来说却是可以满足要求.在执行类成员或继承的类组件的复制时,派生类会使用基类的拷贝构造函数来复制派生类对象中的基类部分;
(3)对于赋值来说,也是如此.

2. 派生类使用了new:派生类需要显式定义析构函数,拷贝构造函数和赋值运算符.
(1)派生类析构函数自动调用基类的析构函数,故其自身的职责是对派生类构造函数执行的工作进行清理.
(2)拷贝构造函数同理,调用基类相应的拷贝构造函数复制基类部分,派生类部分只能由派生类的拷贝构造函数访问并拷贝.
(3)对于赋值来说,也是如此.

// 10 ///////////////////////////////////////////////////////////////////////
包含和私有继承的比较:
1. 包含提供被显式命名的对象成员;而私有继承提供没有名称的子对象成员.
2. 对于构造函数,包含将使用成员对象赋值语句;继承使用的是成员初始化列表语法,它使用类名而不是成员名来标志构造函数.
3. 使用包含时将使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法.
4. 访问基类对象.包含可以直接使用对象名访问;私有继承没有名称,通过强制类型转换,将派生类对象转换为基类对象,进而进行访问.
5. 同第4点,访问基类的友元函数,私有继承可以通过将派生类对象转换成基类对象,匹配基类友元函数参数,进而调用到基类友元函数的目的.
6. 对于程序员来说,包含易于理解,我们可以显式地在包含类中定义被包含类的对像,然后用该对像调用被包含类的成员函数,这会让程序员看上去非常清淅易懂,但私有继承私不这么直观,它使类之间的关系变得更加抽像,并且很多情况下,这种关系比较抽像和复杂,程序员必须处理很多继承带来的问题,比如说派生类与基类的函数重名问题,也就是两义性问题,或者继承一些不该继承的方法或变量,比如说派生类的某个基类上还有基类,那么这个基类的方法也会延续到后面的所有派生类中.显然,作为一个位地继承层次之外的类,包含类则不存在这样的问题.
7. 包含类还可以包含同一个类的多个对像,但是私有继承不能这么做.
8. 私有继承有包含所没有的特殊属性,比如说私有继承的派生类与基类是继承与被继承的关系,我们知道公有继承的情况下基类的保护成员在派生类中也是保护成员,我们可以像在基类中一样访问它,私有继承的情况下基类的保护成员在派生类中是私有成员,我们可以在派生类中设置成员函数来访问它,但是包含与被包含却没有这层关系,由于所包含的对像位于继承层次之外,并且保护成员只对派生类开放,因此包含类不能通过所包含的对像来访问该对像的保护成员.
9. 私有继承的另外一个优势是使派生类可以重新定义基类的虚函数,这个被重新定义的虚函数只能在类中使用,包含则没有这个特性.

// 11 ///////////////////////////////////////////////////////////////////////
类型转换运算符
(1)dynamic_cast:在类的继承体系中向上转换(派生类->基类),而不允许其他转换(空指针);
(2)const_cast:删除const限定符;
(3)static_cast:与通用的类型转换运算符相同;
(4)reinterpret_cast:用于危险的类型转换,也可以把指针类型转换为足以存储指针表示的整型.

C++易混淆知识点整理

标签:

原文地址:http://www.cnblogs.com/yyxt/p/5399492.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!