文章目录
类的定义与实例化
C++中的类与C语言中的结构体相似,但是类有一个显著的特征就是里面不仅可以定义成员变量,还可以定义成员函数。
定义
class Test
{
int _val;
void Init() // Init为类的成员函数
{
//....
}
};
类的成员函数也可以做到声明与定义分离,不过函数定义时需要指定该函数的类域。
class Test
{
int _val;
void Init(); // 函数声明
};
void Test::Init() // 函数的定义在类外实现
{
//....
}
实例化
如果说类的定义相当于画建筑图纸,那么类的实例化就是在现实世界将这栋建筑完工。
class Test // 类的定义
{
int _val;
};
Test T; // 类的实例化
类的定义并不会向操作系统申请空间,而实例化会。
类的访问限定符
类的三个访问限定符:public
,protected
,private
。
public
:可以被外部程序访问。protected
:可以被子类访问,但不能被其他不相干的类访问。private
:只能在该类被访问,可以被内部类访问,但不能被子类访问。
在类中如果不指定默认就是私有成员,而结构体默认公有。
class Test
{
private:
int _val1;
public:
void Init()
{
//....
}
};
访问限定符是类进行封装的一个手段。
面向对象有三大特性:封装、继承、多态。
类和对象主要研究的就是“封装”。即:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。如果想要访问对象的内部信息,必须通过严格的接口或方法来实现。
对象在类中的存储
对于类中的成员变量,存储方式和结构体是相同的,遵循内存对齐原则。
而对于成员函数,是放在代码段上,受访问作用域限定。
class Test
{
private:
int _val1;
public:
void Init()
{
//....
}
};
这个Test类的大小是4个字节。也就是说,计算类的大小时并不会算上成员函数。
类的成员函数
this指针
class Test
{
private:
int _val;
public:
void Init(int val)
{
_val = val;
// 等价于 this._val = val
}
};
///
Test T;
T.Init(1);
以上的Init函数实际上有两个参数,一个是传入的val,另一个就是this指针。
成员函数通过this指针this指针来访问这个类中的变量。
我们可以在函数中使用this指针,但不能改变this指针的指向。
类的成员函数都有this指针,但static成员函数没有。
类的默认成员函数
在创建类的时候,有一些默认成员函数。本文着重于以下四个——构造函数、析构函数、拷贝构造、赋值重载。
如果我们没有实现这些函数,编译器会帮我们自动实现。我们实现了编译器就不会在生成。
构造函数
构造函数的功能是帮我们初始化变量。
构造函数的函数名应当和类名相同,并且没有返回值(不是void)
class Test
{
private:
int _val;
public:
Test(int val = 0)// 自定义实现的构造函数(全缺省),编译器就不会再实现
{
_val = val;
}
};
这段代码是正确的。
class Test
{
private:
int _val;
public:
Test(int val)// 自定义实现的构造函数,编译器就不会再实现
{
_val = val;
}
};
///
Test T;
这段代码不能编译通过这个T对象没有合适的默认构造函数可用,因为它传入的参数和这个构造函数不对应。
Test T(1);
这个是对的,因为对于这个T对象来说,Test函数与它传入的参数相同,Test()就是合适的构造函数。
那编译器自动生成的默认构造函数能做什么呢?
- 对于内置类型(int,char等)变量,编译器不做处理。
- 对于自定义类型(自己定义的类、结构体等)变量,编译器调用它们的构造函数。
class Stu
{
private:
char _name[20];
char _id[10];
public:
Stu()
{
cout << "Stu()" << endl;
}
}
class Test
{
private:
int _val;
Stu _s;
};
int main()
{
Test T;
return 0;
}
以上代码的结果是输出一个Stu(),也就印证了编译器生成的构造函数会调用自定义类型的构造函数
初始化列表
class Test
{
private:
int _val;
int _num;
int _code;
public:
Test(int val = 0, int num = 0, int code = 0)
:_val(val)
,_num(num)
,_code(code)
{
if(val < 0 || num < 0 || code < 0)// 函数体内可以检验数据合法性
exit(-1);
}
};
- 初始化时先走初始化列表后进入函数体
- 成员变量初始化顺序是按照对象声明顺序,与初始化列表顺序无关
- 每个成员变量在初始化列表只出现一次
- 引用成员变量、const成员变量必须走初始化列表(它们不能再次修改)
析构函数
析构函数用来完成对象销毁。
析构函数的函数名是类名前面加上一个~,没有返回值(不是void)
class Test
{
private:
int _val;
public:
~Test()// 自定义实现的析构函数,编译器就不会再实现
{
cout << "~Test()" << endl;
}
};
和构造函数类似,我们实现了析构函数,编译器就不会在实现了。
编译器实现的析构函数和构造函数类似
- 对于内置类型,不做处理
- 对于自定义类型,则调用它们的析构函数。
值得一提的是,析构函数的调用顺序和构造函数是相反的。
储存在不同域的对象析构函数的调用顺序会发生变化:局部对象->局部静态对象->全局对象。
拷贝构造
拷贝构造函数用来完成将一个对象赋值给另外一个对象的工作。
拷贝构造函数名和类名一样,没有返回值,只接受被拷贝对象的引用作为参数。
class Test
{
private:
int _val;
public:
Test(Test& T)// 这里如果使用传值调用,会导致无限递归。
{
_val = T._val;
}
};
如果我们没有实现拷贝构造函数,编译器会自动生成一个。
- 编译器自动生成的拷贝构造函数完成对象的浅拷贝(拷贝值)。
- 在需要深拷贝的场景,我们还是得自己实现。
赋值运算符重载
赋值运算符重载是运算符重载的一种。
什么是运算符重载?
我们日常用的 +、-、*、/ 等运算符都是运用于内置类型的,如果类想要运用这些运算符,那么就得进行运算符重载。
例如以Test类为例重载赋值运算符=
class Test
{
private:
int _val;
public:
Test& operator=(const Test& t)// 返回引用方便连续赋值,作为类的成员函数有this指针
{
_val = t._val;
return *this;
}
};
有5个运算符是不能够重载的:.*
、::
、sizeof
、?:
、.
C/C++中没有的符号不能进行重载。
如果我们没有实现赋值运算符重载,编译器会自动生成。
- 对于内置类型,进行浅拷贝。
- 对于自定义类型,调用它们的赋值运算符重载。
注意:运算符重载中的前置++和后置++
为了区别前置++和后置++
C++语法规定后置++重载有一个int类型的参数,但是使用时不需要手动传。
class Test
{
private:
int _val;
public:
Test& operator++()// 前置++(先++,再返回++后的引用)
{
_val += 1;
return *this;
}
Test operator++(int)// 后置++(++后返回之前的值)
{
Test tmp(*this);
_val += 1;
return tmp;// 不返回引用是因为出作用域tmp要销毁
}
};
const成员函数
本质上是对this指针的修饰,表示不能修改this指针指向的对象。
class Test
{
private:
int _val;
public:
Test operator++(int) const// 这里this._val就不能修改了
{
Test tmp(*this);
_val += 1;
return tmp;
}
};
static成员
class Test
{
private:
int _val;
static int _num;
public:
static int GetNum()
{
return _num;
}
};
int Test::_num = 0;
- static成员为该类所有对象共享,不属于任何对象,存放在静态区,但是受到访问限定符限制。
- static成员一定要在类外定义和初始化。
- static成员函数没有this指针,除了类中的static成员变量,不能访问其他任何变量
explicit关键字
class Test
{
private:
int _val;
public:
explicit Test(int val = 0)
{
_val = val;
}
};
int main()
{
Test T1 = 1; // 这一种赋值方式之所以可以是因为发生了隐式类型转换
Test T2(1);
return 0;
}
而加了explicit关键字,就禁止了构造函数的隐式类型转换,T1没有合适的构造函数会报错,T2不会。
友元
友元函数
友元函数相当于类给外面的函数开了个特权,能够让其访问内部私有或受保护的变量。
class Test
{
friend ostream& operator<<(ostream& out, const Test& T);
private:
int _val;
};
ostream& operator<<(ostream& out, const Test& T)
{
out << T。_val << endl;
return out;
}
友元类
友元函数相当于类给外面的类开了个特权,能够让其访问内部私有或受保护的变量。
class Test
{
friend class A;
private:
int _val;
};
class A
{
int _num;
Test a;
public:
void GetVal()
{
cout << a._val << endl;
};
};
内部类
一个类被定义在另一个的里面,就叫做内部类。
class A
{
static int _val;
class B // 注意与 “B _val;” 区分
{
int _num;
void GetVal()
{
cout << _val <<endl;
}
};
};
- 内部类是外部类的友元类。B可以访问A的变量
- B可以直接访问A的static成员变量,不需要指定类域
- 以上
sizeof(A) == 4
,A的大小不包括B