初识C++02:类和对象
类和对象
创建方法
第一种式直接声明:
网站建设哪家好,找创新互联!专注于网页设计、网站建设、微信开发、重庆小程序开发、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了梅州免费建站欢迎大家使用!
class Student {
private:
string name;
public:
Student(string name) {
this->name = name;
}
}
//声明:
Student lu;//不带初始化
//带初始化;
Student lu(huang);
Student lu = Student(huang);
//访问方式
lu.name;
这样的声明方式,是将对象在栈上创建,栈内存自动管理,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束后在将这些局部变量的内存空间回收。在栈上分配内存空间效率很高,但是分配的内存容量有限。
第二种使用对象指针声明
Student *plu = new Student;//不带初始化;
Student *plu = new Student(huang);//带初始化;
//访问方式
plu->name;
这样声明,是将对象在堆中创建,堆内存代码人员管理,new和delete配对使用。使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。
函数声明
成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前。因为类体内定义的函数默认是内联函数,一般用内联函数的时候才会在类内部实现;例子:
class Student {
private:
string name;
int age;
public:
//函数声明
Student(string name, int age);
}
//函数定义
Student::Student(string name, int age) {
this->name = name;
this->age = age;
}
类成员的访问权限以及封装
在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。
在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。
成员变量大都以m_
开头,这是约定成俗的写法,易区分。
建议在开发中不需要暴暴露出来的属性和方法都写成private;
给成员变量赋值的函数通常称为 set XXX函数,读取成员变量的值的函数通常称为 get XXX函数,XXX表示变量名;类中的不写private这些关键词默认是private;
class Student {
private:
string m_name;
public:
void setname(string name);
void getname;
}
void Student::setname(string name) {
m_name = name;//这里可以直接用m_name;
}
.....
对象内存模型和函数编译原理及实现
编译器会将成员变量和成员函数分开存储:分别为每个对象的成员变量分配内存,但是所有对象都共享同一段函数代码,节省空间,sizeof一个对象大小就是全部成员变量的总和大小;
C++和C语言的编译方式不同。C语言中的函数在编译时名字不变,或者只是简单的加一个下划线_
(不同的编译器有不同的实现),c++是通过一种特殊的算法来实现的,对函数重命名(这个过程叫字编码(Name Mangling));下图是一个编译器重命名的方式,?方法@类名.....
从上图可以看出,成员函数最终被编译成与对象无关的全局函数,如果函数体中没有成员变量,不用对函数做任何处理,直接调用即可。
如果有成员变量(它的作用域不是全局的),C++规定,编译成员函数时要额外添加一个参数,把当前对象的指针传递进去,通过指针来访问成员变量(实际上传递的就是this指针)
void Demo::display(){
cout<
会编译为类似:
void new_function_name(Demo * const p){//const表示指针不能被修改;
//通过指针p来访问a、b
cout<a<
这样通过传递对象指针就完成了成员函数和成员变量的关联。这与我们从表明上看到的刚好相反,通过对象调用成员函数时,不是通过对象找函数,而是通过函数找对象。
构造函数
构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用;
一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。
构造函数定义由两种写法:正常函数写法和使用构造函数初始化列表
//第一种
Student::Student(char *name, int age){
m_name = name;
m_age = age;
}
//第二种
Student::Student(char *name, int age): m_name(name), m_age(age){}
注意????第二种:成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关;如:
class Demo{
private:
int m_a;
int m_b;
public:
Demo(int b);
void show();
};
Demo::Demo(int b): m_b(b), m_a(m_b){
m_a = m_b;
m_b = b;
}
//错误,给 m_a 赋值时,m_b 还未被初始化,它的值是不确定的,所以输出的 m_a 的值是一个奇怪的数字;给 m_a 赋值完成后才给 m_b 赋值,此时 m_b 的值才是 值b。
//obj 在栈上分配内存,成员变量的初始值是不确定的。
使用构造函数初始化列表并没有效率上的优势,但是书写方便,而且,初始化 const 成员变量的唯一方法就是使用初始化列表,原因:为什么要用初始化列表
析构函数
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。
class VLA{
public:
VLA(int len); //构造函数
~VLA(); //析构函数
private:
const int m_len; //数组长度
int *m_arr; //数组指针
int *m_p; //指向数组第i个元素的指针
};
VLA::VLA(int len): m_len(len){ //使用初始化列表来给 m_len 赋值
if(len > 0){ m_arr = new int[len]; /*分配内存*/ }
else{ m_arr = NULL; }
}
VLA::~VLA(){
delete[] m_arr; //释放内存
}
通过直接用类声明的对象在栈中,出了作用域(比如说函数return了),就会调用析构函数;在全局建的对象在.data区,程序结束后才释放; 注意????????♂️:两中方法调用析构函数的顺序都是先生成的后析构,后生成的先析构;
new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数,例如在main函数中new对象然后delete对象;如果声明了变量在堆中,不通过析构函数释放内存,即使外面delete了,也只是删除了指针,里面的空间还是被占用着,没有被释放掉,如上面的int[len];
此处补充指针知识:
int a = 10;
int* p = &a;//p是指针,这个*表示是int的指针类型;
此时 *p = 10//这里的*说明是这个指针指向的对象,与上面*大大不同;而p只是以一个地址
成员对象和封闭类
一个类的成员变量如果是另一个类的对象,就称之为“成员对象”。包含成员对象的类叫封闭类。
创建封闭类的对象时,它包含的成员对象也需要被创建,这就会引发成员对象构造函数的调用,对于没有默认构造函数的成员对象,必须要使用封闭类构造函数的初始化列表!!!
类名::构造函数名(参数表): 成员变量1(参数表), 成员变量2(参数表), ...
{
//TODO:
}
一定要用初始化列表的四种情况
初始化时:封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数;
消亡时:先执行封闭类的析构函数,然后再执行成员对象的析构函数,刚刚好和创建相反。
this指针、static关键字
C++ 中的一个关键字,也是一个 const 指针, 所以要用->
来访问成员变量或成员函数。它指向当前对象,通过它可以访问当前对象的所有成员。
this的本质:this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
上述中函数编译原理:成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。
static修饰成员变量:
-
一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。
-
static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。
-
静态成员变量必须初始化,而且只能在类体外进行。例如:int Student::m_total = 10;初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。
-
静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。
static修饰成员函数:
静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
原因:编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把当前对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数,所以静态成员函数也无法访问普通成员变量,只能访问静态成员(在全局)。
const 成员变量/成员函数(常成员函数)/对象
const成员变量:加上 const 关键字。初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表
const成员函数: const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。一般类中的get函数都设置为常成员函数,只读不给改;
-
函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如
const char * getname()
。 -
函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如
char * getname() const
class Student { public: Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score) {}//方法实现都不写; char *getname() const; prviate: char *m_name; int m_age; float m_score; }; char* Student::getname() const { return m_name; }//方法实现都不写;
const对象: const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)了,因为非 const 成员可能会修改对象的数据(编译器也会这样假设),C++禁止这样做。
友元函数和友元类
借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。在当前类以外定义的、不属于当前类的函数也可以在类中声明,但要在前面加 friend 关键字,这样就构成了友元函数。友元函数可以是不属于任何类的 非成员函数,也可以是其他类的成员函数。
- 将非成员函数声明为友元函数
class Student{
public:
Student(char *name, int age, float score);
public:
friend void show(Student *pstu); //将show()声明为友元函数
private:
char *m_name;
int m_age;
float m_score;
};
//非成员函数
void show(Student *pstu){//属于全局函数,通过参数传递对象,可以访问private成员变量
cout<m_name<<"的年龄是 "<m_age<<",成绩是 "<m_score<
- 将其他类的成员函数声明为友元函数,该成员函数提供给一个类用
class Address; //一定要提前声明Address类
//声明Student类
class Student{
public:
Student(char *name, int age, float score);
public:
void show(Address *addr);//要使用的类,前面有声明address所以不会报错!!!!!!!!!!!!!!!!!
private:
char *m_name;
int m_age;
float m_score;
};
//声明Address类
class Address{
private:
char *m_province; //省份
char *m_city; //城市
char *m_district; //区(市区)
public:
Address(char *province, char *city, char *district);
//将Student类中的成员函数show()声明为友元函数
friend void Student::show(Address *addr);//!!!!!!!!!!!!!!!!!!!
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
cout<m_province<<"省"<m_city<<"市"<m_district<<"区"<
友元类:
将类 B 声明为类 A 的友元类,那么类 B 中的所有成员函数都是类 A 的友元函数,可以访问类 A 的所有成员,包括 public、protected、private 属性的
class Address; //提前声明Address类
//声明Student类
class Student{
public:
Student(char *name, int age, float score);
public:
void show(Address *addr);
private:
char *m_name;
int m_age;
float m_score;
};
//声明Address类
class Address{
public:
Address(char *province, char *city, char *district);
public:
//将Student类声明为Address类的友元类
friend class Student;
private:
char *m_province; //省份
char *m_city; //城市
char *m_district; //区(市区)
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
cout<m_province<<"省"<m_city<<"市"<m_district<<"区"<
- 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
- 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。
- 除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。
ps: 其实类也是一种作用域 , 普通的成员只能通过对象(可以是对象本身,也可以是对象指针或对象引用)来访问,静态成员既可以通过对象访问,又可以通过类访问,而 typedef 定义的类型只能通过类来访问
struct和class的区别
C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
- 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承(到继承会讲)。
- class 可以使用模板,而 struct 不能(到模板会讲)。
建议使用 class 来定义类,而使用 struct 来定义结构体,这样做语义更加明确
本文名称:初识C++02:类和对象
路径分享:http://abwzjs.com/article/dsoipgp.html