1.常量对象,常量成员函数
(1).常量对象
常量对象的引用和指针不能调用类的普通的成员函数。只能调用常量成员函数。
(2).常量成员函数:把const放在类成员函数参数列表后。表示隐含的this是一个指向常量的指针
(3).当创建一个const对象时,直到构造函数完成初始化过程,对象才取得其常量属性。
2.友元
(1).类可以允许其他类或函数访问它的非公有成员,方法是令其他类或函数成为它的友元。
(2).友元:在类内为对应函数(成员函数或非成员函数)或类加上一个friend开头的声明。
(3).把一个A类的成员函数FunA声明为令一个类B的友元时,需要组织下定义和声明顺序。
a.定义A。
b.定义B,声明A::FunA为其友元。
c.定义A:FunA。
3.内联函数
类内部定义函数的默认为内联的
类外部定义函数时,加上inline修饰使其内联。
内联是否有效取决于编译器判断。
4.可变数据成员
mutable 类型 变量名;
然后可以在const成员函数里访问和修改此数据成员。
5.类内初始值
class x
{
private:
int a = 0;
vector<int> b{0};
};
6.类声明
类在声明后定义前这段区间属于不完全类型。此时只能,定义指向此类型的指针或引用;声明以其为形参或返回类型的函数。
类内部不能有类自己的成员声明。指向类自己的指针或引用可以。
class A;// ok
A* p = nullptr;// ok
A& pp = *p;// ok
A fun(A a);// ok
class A
{
private:
A m_ppp;// err
A* m_p;// ok
A& m_pp;// ok
int m_i;
};
int main()
{
return 0;
}
7.类的定义分两步处理:
(1).编译成员的声明。
(2).直到类全部可见才编译函数体,意味着函数体内可以使用类中任何成员。
成员函数的返回类型或参数列表中名字,须使用前可见。
意味着成员函数的返回类型,形参类型必须在类定义中:
a.前面部分。
b.外层作用域内已经被声明。
class A
{
public:
B fun();// err
void fun(B);// err
void fun()
{
B* p = nullptr;// ok
}
class B;
B fun2();// ok
void fun2(B);// ok
};
int main()
{
}
8.构造函数
(1).对const,引用或某种未提供默认构造函数的类类型的类内成员,则必须通过构造函数初始值列表为这些成员提供初值。
随着构造函数体一执行,初始化就完成了。构造函数体内不属于初始化过程。
(2).类成员初始化顺序和他们在类定义中出现顺序一致。(构造函数初始值列表无法影响初始化顺序)。
(3).默认初始化:
块作用域内定义不含初始值的非静态变量或数组;类类型成员中没在构造函数初始值列表中初始化的。
(4).值初始化:
数组初始化时,初始值数量少于数组大小时,后续数组元素;定义局部静态变量无初始值;显式请求值初始化T()。
(5).默认初始化,值初始化效果
对基本数值类型,默认初始化后,值大小为未知。值初始化后,为0。
对类类型,默认初始化,值初始化均指向默认构造函数。
如果构造函数只接受一个形参,则它实际上定义了通过形参类型自动转化为此类类型的隐式转换机制。
在构造函数声明前加explicit可以阻止此隐式转换发生。定义处不用加explicit。
9.聚合类
(1).条件:
所有成员都是public
没定义任何构造函数
没类内初始值
没基类,没virtual函数
(2).针对这种类型可以成员初始值列表初始化。
10.类的静态成员
(1).特性
类的静态成员存在于任何此类型实例对象之外。
类的静态成员函数不包含this指针。
(2).访问
可通过类对象,引用或指针或作用域运算符来访问静态成员。
成员函数内,可直接访问。
(3).初始化
在类外部初始化静态成员时,不能重复static。
静态数据成员,不是在构造函数中初始化的。必须在类外部定义和初始化每个静态成员。
(4).不同于普通成员处:
静态数据成员可以是不完全类型,类的非静态成员则不可。
可以用静态成员做默认实参,类的非静态成员则不可。
#include <iostream>
class A
{
public:
A(){}
public:
static A m_a;// ok
};
A A::m_a;
void fun(A a = A::m_a)// ok
{
}
int main()
{
return 0;
}
11.成员函数指针
以一个实例讲解成员函数指针使用
#include <iostream>
#include <functional>
class A
{
public:
A()
{
}
void fun(int i,int j)
{
printf("fun:i_%d,j_%d\n", i, j);
}
};
int main()
{
void (A::*pf)(int, int);
pf = &A::fun;
A a;
A *pa = &a;
(a.*pf)(10, 10);// 1
(pa->*pf)(10, 10);// 2
//(*pf)(&a, 1, 2);// 3 err
std::function<void (A*, int, int)> fcn = &A::fun;
fcn(&a, 2, 3);
return 0;
}
注意直接使用成员指针时,只能通过1,2的形式调用。不支持3的形式调用。
12.c++对象分配和释放
不同于c中使用malloc和free完成线性空间申请和释放。
c++对象分配和释放皆包含两个步骤。
对对象分配,先分配存储空间,再执行构造。
对对象释放,先执行析构,再释放存储空间。
我们使用new,delete时,单个操作已经包含两个步骤。
若需要显式将两个步骤分开处理,可按下述实例方式操作。
这种显式拆分有助于统一管理内存分配,对象构造。一些c++内存分配器正是基于此来统一管理内存分配,释放;划分内存管理阶段,构造析构阶段。
#include <iostream>
class Norm1
{
public:
Norm1():m_c('n')
{
printf("Norm1()_%c\n", m_c);
}
Norm1(char c):m_c(c)
{
printf("Norm1(char)_%c\n", m_c);
}
~Norm1()
{
printf("~Norm1()_%c\n", m_c);
}
private:
char m_c;
};
class Norm2
{
public:
Norm2():m_c('o')
{
printf("Norm2()_%c\n", m_c);
}
Norm2(char c):m_c(c)
{
printf("Norm2(char)_%c\n", m_c);
}
~Norm2()
{
printf("~Norm2()_%c\n", m_c);
}
private:
char m_c;
};
class Base
{
public:
Base():m_c('n')
{
printf("Base()_%c\n", m_c);
}
Base(char c):m_c(c)
{
printf("Base(char)_%c\n", m_c);
}
~Base()
{
printf("~Base()_%c\n", m_c);
}
private:
char m_c;
};
class A : public Base
{
public:
A():Base()
{
printf("A()\n");
}
A(char c):Base(c), n1(c), n2(c)
{
printf("A(char)\n");
}
~A()
{
printf("~A()\n");
}
private:
Norm1 n1;
Norm2 n2;
};
int main()
{
A* p = (A*)operator new(sizeof(A));
printf("now cons\n");
new (p) A();
p->~A();
printf("now free\n");
operator delete(p);
A* pa = (A*)operator new(sizeof(A)*10);
printf("now cons 10 times\n");
for(int i = 0; i < 10; i++)
{
new (pa + i) A('a'+i);
}
for(int i = 0; i < 10; i++)
{
(pa + i)->~A();
}
printf("now free\n");
operator delete(pa);
return 0;
}