C++笔记(三)类和对象1
封装
就是class类,类中需要包含成员属性(成员变量)和成员行为(成员函数),还有访问权限:public、private、protected。如果不写访问权限,默认就是private权限 访问权限:
public:类内和类外都可以访问 private:类内可以访问,类外不可以访问 protected:类内可以访问,类外不可以访问(在后续的继承讲解) class类与struct结构体的区别:class类可以选择访问权限,且默认是私有访问权限;struct结构体默认是公共访问权限 访问私有成员的方法:可以在公共成员函数写私有成员变量的读和写函数 一般习惯把成员变量放在私有访问权限中,然后根据成员变量是否可读还是可写,写出对应的可读(get)和可写(set)函数
class a {
public :
int val;
void func1 ( ) ;
private :
void func2 ( ) {
. . .
}
} ;
void a:: func1 ( ) {
. . .
}
对象的初始化和清理
构造函数
语法:类名(){}
构造函数,没有返回值也不写void 函数名称与类名相同 构造函数可以有参数,因此可以发生重载 程序在创建对象的时候会自动调用构造函数,且只会调用一次 如果没有写构造函数,编译器会自动生成一个空的构造函数
析构函数
语法:~类名(){}
析构函数,没有返回值也不写void 函数名称与类名相同,在名称前添加~ 析构函数不可以有参数,因此不会发生重载 程序在对象销毁前会自动调用析构函数,且只会调用一次 如果没有写析构函数,编译器会自动生成一个空的析构函数
class student {
public :
student ( int a) { }
~ student ( ) { }
} ;
int main ( ) {
student s1;
return 0 ;
}
构造函数的分类
分类:
按照参数分类:有参构造和无参构造 按照类型分类:普通构造和拷贝构造 有参构造函数和无参构造函数根据是否有无参数分类,可以重载 拷贝构造函数有固定的写法 调用:
class Person {
public :
Person ( ) { }
Person ( int a) { }
Person ( const Person & p) { }
~ Person ( ) { }
private :
int age;
} ;
void test01 ( ) {
Person p1;
Person p2 ( 10 ) ;
Person p3 ( p2) ;
Person p1;
Person p2 = Person ( 10 ) ;
Person p3 = Person ( p2) ;
Person p4 = 10 ;
Person p5 = p4;
}
拷贝构造函数调用时机
使用一个已经创建完毕的对象来初始化一个新的对象,就是传统的复制概念 值传递的方式给函数参数传值,会调用拷贝构造函数 值方式返回局部对象,会调用拷贝构造函数
class Person (
public :
Person ( ) { }
Person ( int a) { }
Person ( const Person & p) { }
~ Person ( ) { }
) ;
void test1 ( ) {
Person p1 ( 20 ) ;
Person p2 ( p1) ;
}
void dowork1 ( Person p) {
}
void test2 ( ) {
Person p;
dowork1 ( p) ;
}
Person dowork2 ( ) {
Person p1;
return p1;
}
void test3 ( ) {
Person p = dowork2 ( ) ;
}
构造函数调用规则
默认写一个类的情况下,编译器至少会添加3个函数:
默认构造函数(无参数,函数体为空) 默认析构函数(无参数,函数体为空) 默认拷贝构造函数,对属性值进行拷贝 规则如下:
如果用户定义有参构造函数,编译器就不会提供默认构造函数,但是会提供默认的拷贝构造函数
就是说,你写了有参构造函数,而你调用默认构造函数时,编译器会找不到,但是拷贝构造函数还是有的 如果用户定义拷贝构造函数,编译器不会提供其他构造函数
跟上面一样,如果写了拷贝构造函数,而你调用了其他构造函数,编译器就找不到了
深拷贝和浅拷贝(面试经典)
浅拷贝:简单的复制拷贝操作(默认的拷贝构造函数) 深拷贝:在堆区重新申请空间,进行拷贝操作 浅拷贝带来的问题:重复的释放同一段堆区内存,使得出现非法操作
class Person ( ) {
public :
Person ( ) { }
Person ( int a, int height) {
m_age = a;
m_height = new int ( height) ;
}
~ Person ( ) {
if ( m_height != NULL ) {
delete m_height;
m_height = NULL ;
}
cout << "析构函数" << endl;
}
int m_age;
int * m_height;
} ;
void test1 ( ) {
Person p1 ( 10 , 160 ) ;
Person p2 ( p1) ;
cout << * p2. m_height << endl;
}
上面的代码就会出现浅拷贝的问题,程序会出错:非法操作。
首先是堆区的数据是先入后出 的,所以释放时也是先释放的p2中的(其实可以不管这个顺序的,因为指向的是同一个堆区数据) 然后我们没有写拷贝构造函数,因此p2的拷贝是简单的赋值拷贝(浅拷贝),而m_height参数是一个指针(地址),仅仅将地址赋值给p2了,只不过这个地址指向的是一个堆区 最后就是问题所在,p2被释放的时候,析构函数将m_height指向的堆区数据释放了,然而在p1被释放的时候,析构函数又一次将m_height指向的堆区数据释放,出现了释放一块未指定的区域,造成非法操作。下面用图展示:
下面就使用深拷贝的方式解决这个问题,就是让p2指向另一块堆区数据,数据值相同,但是这样两次释放数据就不会重复释放同一块了。用图展示:
Person ( const Person & p) {
m_age = p. m_age;
m_height = new int ( * p. m_height) ;
}
初始化列表
语法:构造函数():属性1(值1),属性2(值2) ... {}
可以有更加灵活的方式
class Person {
public :
Person ( ) : m_a ( 10 ) , m_b ( 20 ) {
}
Person ( int a, int b) : m_a ( a) , m_b ( b) {
}
int m_a;
int m_b;
} ;
void test1 ( ) {
Person p;
Person p ( 10 , 20 ) ;
}
类对象作为类成员
就是在类中嵌套了另一个类,如,B类中有一个A类
构造函数的执行顺序:先执行类中成员的构造函数,再执行外面类的构造函数。就是说,先执行A的,再执行B的 析构函数的执行顺序:先执行类外的析构函数,在执行类中成员的析构函数。就是说,先执行B的,在执行A的
class Phone ( ) {
public :
string m_pname;
} ;
class Person ( ) {
public :
Person ( string name, string p) : m_name ( name) , m_phone ( p) {
. . .
}
string m_name;
Phone m_phone;
} ;
void test ( ) {
Person ( "asdsa" , "sdasdd" ) ;
}
静态成员
静态成员变量
特点:
所有对象共享一份数据,就是说,不是某一个对象的数据,而是所有对象共享的,任意对象修改,都会更改其他对象,就是一块内存。 在编译阶段分配内存 类内声明,类外初始化,使用类的::域运算符在类外对静态成员变量初始化,而如果是私有访问权限的,不能这么初始化,可以使用成员函数进行初始化赋值
静态成员变量的访问方式
通过对象的方式访问,就是正常的成员变量的访问方式 通过::运算符访问
class Person {
public :
static int m_a;
} ;
int Person:: m_a = 10 ;
void test1 ( ) {
Person p1;
p1. m_a = 100 ;
Person p2;
p2. m_a = 200 ;
cout << Person:: m_a << endl;
}
静态成员函数
所有对象共享同一个函数 静态成员函数只能访问静态成员变量
class Person ( ) {
public :
static void func ( ) {
m_a = 100 ;
}
static int m_a;
int m_b;
} ;
int Person:: m_b = 10 ;
void test ( ) {
Person p1;
p1. func ( ) ;
Person:: func ( ) ;
}
C++对象模型和this指针
成员变量和成员函数分开存储
类的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上,就是说只有普通的成员变量才在类的对象上
class Person { } ;
class Person {
int m_a;
} ;
class Person {
int m_a;
static int m_b;
} ;
class Person {
int m_a;
static int m_b;
void func ( ) ;
} ;
class Person {
int m_a;
static int m_b;
void func ( ) ;
static void func1 ( ) ;
} ;
this指针的用途
this指针指向被调用成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针 this指针不要定义,直接使用就行 用途:
当成员函数的形参与成员变量同名时,可用this指针用来区分 在类的非静态成员函数中返回对象本身,可使用return *this
class Person {
Person ( int age) {
this - > age = age;
}
void Personaddage ( Person & p) {
this - > age + = p. age;
}
Person& Personaddage ( Person & p) {
this - > age + = p. age;
return * this
}
int age;
} ;
void test ( ) {
Person p1 ( 10 ) ;
Person p2 ( 10 ) ;
p2. Personaddage ( p1) ;
p2. Personaddage ( p1) . Personaddage ( p1) . Personaddage ( p1) ;
}
空指针访问成员函数
空指针可以调用成员函数,但是需要注意在函数中是否使用this指针,实际上成员变量在函数中,编译器都会在前面添加一个this-> 如果有使用this指针,需要在函数调用开头,进行判断
class Person {
void showPerson1 ( ) {
cout << "asdad" << endl;
}
void showPerson2 ( ) {
cout << m_age << endl;
}
void showPerson3 ( ) {
if ( this == NULL ) {
return ;
}
}
int m_age;
} ;
void test ( ) {
Person * p = NULL ;
p- > showPerson1 ( ) ;
p- > showPerson3 ( ) ;
}
const修饰成员函数
常函数:
成员函数后面加const,就是常函数 常函数内不可以修改成员变量 如果有特殊的成员变量需要常函数修改,需要在成员变量前加mutable(强制转换const) 常对象
声明对象前加const,就是常对象 常对象只能调用常函数(因为如果能够调用普通成员函数,而普通成员函数能够修改成员变量,那么const常对象就无意义了) 常对象依然可以修改mutable的成员变量
class Person {
public :
void shuwPerson ( ) const {
m_b = 20 ;
}
int m_a;
mutable int m_b;
} ;
void test ( ) {
const Person p;
p. m_b = 200 ;
}
友元
全局函数做友元
class Person {
friend void func ( Person * p) ;
public :
int m_a;
private :
int m_b;
} ;
void func ( Person * p) {
p- > m_a = 10 ;
p- > m_b = 20 ;
}
类做友元
class Phone { } ;
class Person {
friend class Phone ;
} ;
成员函数做友元
class Phone {
public :
void show ( ) ;
} ;
class Person {
friend void Phone:: show ( ) ;
} ;