C++基础知识(1)

New和Delete

  • 内置类型对象或未提供默认构造函数的类类型对象必须显示初始化

    1
    2
    int* a = new int;		//a未初始化
    int* b = new int(); //b初始化为0
  • delete后,应该将指针赋值为NULL,否则该指针成为“悬垂指针”,悬垂指针是指向曾经存放对象的内存,但该对象已经不再存在了。

  • C++保证:删除0值的指针是安全的

显示类型转换

  • 调用方式: cast-name(expression)
  • static_cast
    编译器隐式的执行任何类型转换

    1
    2
    void* p = &d;
    double* dp = static_cast<double*>p;
  • dynamic_cast

  • const_cast
    转换掉表达式的const性质
  • reinterpret_cast

    类成员函数的重载

  • 函数名相同,函数的参数不同,返回值可以不同。注意:基于成员函数是否为const,可以重载一个成员函数

类的inline函数

  • 编译器会自动展开函数的定义。注意点:一般函数的代码量很小,且该函数会被经常调用。同时,该函数的定义必须被调用它的源文件可见。通常,内联函数的定义是放在某公共头文件中。

类的成员使用类声明

  • 只有当类的定义已经在前面出现过数据成员才能被指定为该类类型。如果该类型是不完全类型(即只声明未定义),那么数据成员只能是指向该类类型的指针或者是引用。

类的构造函数初始化

  • 构造函数的初始化列表会在构造函数体内语句之前运行。
  • 如果没有显式初始化列表,那么对于类类型的成员变量使用该成员变量的默认构造函数初始化。对于内置类型或者是复合类型的成员变量根据对象是否为全局而定。(全局对象则会初始化,局部对象不会初始化)
  • 注意:对于const成员变量或者是引用类型成员变量及没有默认构造函数的类类型成员变量必须在显式初始化列表。

构造函数的隐式转换

  • 一般只带一个参数的构造函数会出现隐式转换,或者是构造函数的第二参数带有默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A
{
public:
A()
{}
A(int a)
{
num = a;
}
private:
int num;
};

A a = 12 //隐式的调用构造函数,再调用复制构造函数
A b(12); //显示的调用构造函数
  • 隐式转换一般会带来不好的影响,最好的处理方法是加上explicit关键字,指定构造函数不能隐式转换。

友元

  • 允许一个类将对其非公有成员的访问授予指定的函数或类

static类成员

  • 非static数据成员存在于类类型的每一个对象中。static数据成员独立于该类的任意对象而存在;每个static数据成员与类关联的对象,并不与类的对象关联。
  • static成员函数只能访问static成员变量,不能直接使用非static成员变量。

复制构造函数

  • 调用时间

    • 根据另一个同类型的对象显示或者是隐式初始化一个对象
    • 复制一个对象,将它作为实参传给一个函数
    • 从函数返回时复制对象
    • 初始化顺序容器中的元素

      1
      std::vector<std::string>(10);//先调用string的默认构造函数,再调用复制构造函数
    • 根据元素初始化式初列表初始化数组元素

      1
      2
      A a[] = {std::string("abc"),std::string("bca")};
      //首先调用A中接受string形参的构造函数初始化一个临时对象,再调用A的复制构造函数初始化
  • 具体例子
1
2
string null_book = "99999";//首先调用接受一个C风格字符串形参的构造函数,再调用复制构造函数
string empty_copy = string();//首先调用默认构造函数,再调用复制构造函数

智能指针

  • 当类中含有指针成员的时候,不重写赋值构造函数,复制构造函数,析构函数的话,很容易使程序崩溃
  • 原因: 编译器为我们合成的复制、赋值函数,都只是浅层拷贝,不是深层拷贝(指针指向同一个对象),因此往往会在析构的时候,出现异常。
  • 解决方法1:定义智能指针,引入使用计数概念
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class U_Ptr
{
private:
friend class HasPtr;
int *ip;
size_t use;//使用计数
U_Ptr(int* p) : ip(p) , use(1)
{

}
~U_Ptr()
{
delete ip;
}
};

class HasPtr
{
public:
HasPtr(int*p , int i) : ptr(new U_Ptr(p)) , val(i)
{

}
HasPtr(const HasPtr& orig) : ptr(orig.ptr), val(orig.val)
{
ptr->use++;
}
HasPtr& operator=(const HasPtr& rhs)
{
++rhs.ptr->use;
if(--ptr->use == 0)
delete ptr;
ptr = rhs.ptr;
val = rhs.val;
return *this;
}
~HasPtr()
{
if(--ptr->use == 0)
{
delete ptr;
}
}
private:
U_Ptr *ptr;
int val;
};
  • 解决方法2”实现深层拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class HasPtr
{
private:
int* ptr;
int val;
public:
HasPtr(const int& p, int i) : ptr(new int(p)) , val(i)
{

}
HasPtr(const HasPtr& orig) : ptr(new int(*orig.ptr)) , val(orig.val)
{
//实现深拷贝
}
HasPtr& operator=(HasPtr& rhs)
{
ptr = rhs.ptr;
val = rhs.val;
return *this;
}

};

protected成员

  • 基类的一些成员希望允许派生类访问但是禁止其他用户访问,对于这样的成员应使用受保护的访问标志
  • 注意点:派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Test_base
{
public:
Test_base(){}
~Test_base(){}
protected:
int pb;
private:
int kk;
/* data */
};

class Test_sun : public Test_base
{
public:
void fun(Test_sun& t, Test_base& base)
{
t.pb = 1; //派生类对象可以访问基类的protected成员
base.pb = 1;//错误,基类类型的对象不能访问其protected对象
pb = 1;
}
};