C++类的定义和对象
类的成员变量称为类的属性(Property),将类的成员函数称为类的方法(Method)。在面向对象的编程语言中,经常把函数(Function)称为方法(Method)。
类的定义
class Student{ public: //成员变量 char *name; int age; float score; void say(){ cout<<name<<age<<score<<endl; } };
类只是一个模板(Template),编译后不占用内存空间.
class
C++ 中新增的关键字,用来定义类。
成员变量和成员函数,它们统称为类的成员(Member)
创建对象:
Student Lilei; //创建对象
Student
是类名,liLei
是对象名。和使用基本类型定义变量的形式类似, 从这个角度考虑,我们可以把 Student 看做一种新的数据类型,把 liLei 看做一个变量。
在创建对象时,class 关键字可要可不要
class Student LiLei; //正确 Student LiLei; //同样正确
还可以创建对象数组:
Student allStu[100];
使用对象指针:
Student stu; //pStu 是一个指针,它指向 Student 类型的数据,通过 Student 创建出来的对象 Student *pStu = &stu;
在堆上创建对象,这个时候就需要使用前面讲到的new
关键字
Student *pStu = new Student;
使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数.
对象指针后,可以通过箭头->
来访问对象的成员变量和成员函数;
两种创建对象的方式:一种是在栈上创建,形式和定义普通变量类似;另外一种是在堆上使用 new 关键字创建,必须要用一个指针指向它,读者要记得 delete 掉不再使用的对象
C++类的成员变量和成员函数
在定义类的时候不能对成员变量赋值,因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储.
类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内.
只在类体中声明函数,而将函数定义放在类体外面,如下图所示:
class Student{ public: //成员变量; char *name; int age; float score; //声明成员函数; void say(); }; //定义成员函数 void Student::say(){ cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl; }
类体内成员函数默认为内联函数; inline
关键词是多余的;
类体外定义 inline 函数的方式,必须将类的定义和成员函数的定义都放在同一个头(源)文件中.
C++类成员的访问权限以及类的封装
成员访问限定符
- public
- private
- protected
只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分
类的内部(定义类的代码内部):
无论成员被声明为 public、protected 还是 private, 都是可以互相访问的,没有访问权限的限制.
在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员。
#include<iostream> using namespace std; //类声明 class Student{ private: char *m_name; int m_age; float m_score; public: void setname(char *name); void setage(int age); void setscore(float score); }; void Student::setname(char *name){ m_name=name; } void Student::setage(int age){ m_age=age; } void Student::setscore(float score){ m_score=score; }
类的声明和成员函数的定义都是类定义的一部分,在实际开发中,我们通常将类的声明放在头文件中,而将成员函数的定义放在源文件中。
类的封装
根据C++软件设计规范,实际项目开发中的成员变量以及只在类内部使用的成员函数(只被成员函数调用的成员函数)都建议声明为 private,而只将允许通过对象调用的成员函数声明为 public。
另外还有一个关键字 protected,声明为 protected 的成员在类外也不能通过对象访问,但是在它的派生类内部可以访问,这点我们将在后续章节中介绍,现在你只需要知道 protected 属性的成员在类外无法访问即可。
class Student{ private: char *m_name; private: int m_age; float m_score; public: void setname(char *name); void setage(int age); public: void setscore(float score); void show(); };
以上代码完全正确。
C++构造函数
构造函数:特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。
#include <iostream> using namespace std; class Student{ private: char *m_name; int m_age; float m_score; public: Student(char *name, int age, float score); void show(); }; Student::Student(char *name, int age, float score){ m_name = name; m_age = age; m_score=score; } void Student::show(){ cout<<m_name<<m_age<<m_score<<endl; } int main(){ //栈上 Student stu('name',10,10.1f);//不同于python; //堆上 Student *St = new Student('name',10,10.1f); }
构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。
一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。
在栈上创建对象可以写作Student stu()
或Student stu
,在堆上创建对象可以写作Student *pstu = new Student()
或Student *pstu = new Student
,它们都会调用构造函数 Student()
[C++中栈和堆上建立对象的区别]
在C++中类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* p=new A()
静态建立一个类对象,是由编译器为对象在栈空间中分配内存,通过直接移动栈顶指针挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。动态建立类对象,是使用new运算符将对象建立在堆空间中,在栈中只保留了指向该对象的指针。栈是由编译器自动分配释放 ,存放函数的参数值,局部变量的值,对象的引用地址等。
其操作方式类似于数据结构中的栈,通常都是被调用时处于存储空间中,调用完毕立即释放。堆中通常保存程序运行时动态创建的对象,C++堆中存放的对象需要由程序员分配释放,它存在程序运行的整个生命期,直到程序结束由OS释放。
C++构造函数初始化列表(初始化列表是先于构造函数的函数体执行)
使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了。初始化列表可以用于全部成员变量,也可以只用于部分成员变量。
class Student{ private: char *m_name; int m_age; float m_score; public: Student(char *name, int age, float score); }; Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){}
成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关,如下代码;
#include<iostream> using namespace std; class Demo{ private: int m_a; int m_b; public: Demo(int b); }; Demo::Demo(int b): m_b(b), m_a(m_b){ } //Demo::Demo(int b): m_b(b), m_a(m_b){ // m_a=m_b; //m_a 在类中先声明的 // m_b=b; //}
const成员变量只可以初始化列表中初始化
const用于类中成员变量时,将类成员变为只读属性(只读:不能出现在“=”的左边,但在类中仍可以用一个指针来修改其值。) 所以不可以直接在类的构造函数中初始化const 的成员
const成员要不在定义的时候同时初始化,不能进行赋值,就是在初始化列表中对const成员变量成员初始化
#include<iostream> using namespace std; class Demo{ private: const int ci; public: Demo(int len); }; Demo::Demo(int len):ci(len){}
const修饰的局部变量在栈上分配内存空间
const修饰的全局变量在只读存储区分配内储存空间
C++复制构造函数
复制构造函数是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用。
复制构造函数的参数是 const 引用或非 const 引用。 一般使用前者,这样既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。一个类中写两个复制构造函数,一个的参数是 const 引用,另一个的参数是非 const 引用也可以的。
默认复制构造函数:不写复制构造函数,编译器就会自动生成复制构造函数,其作用是实现从源对象到目标对象逐个字节的复制。
若编写了复制构造函数,则默认复制构造函数就不存在
#include<iostream> using namespace std; class Complex{ private: double real; double imag; public: Complex(double r, double i):real(r),imag(i){} Complex(const Complex &c){//编写的复制构造函数, 加const 可以使得此函数接受常量对象 real=c.real; imag=c.imag; } }; int main(){ Complex c1(1,2); Complex c2(c1); return 0; }
从习惯上来讲,复制构造函数还是应该完成类似于复制的工作为好,在此基础上还可以根据需要做些別的操作
Complex (Complex c) {...}//造函数不能以本类的对象作为唯一参数,和复制构造函数相混淆
复制构造函数被调用的三种情况
- 当用一个对象去初始化同类的另一个对象时,会引发复制构造函数被调用
Complex c2(c1); Complex c2 = c1; //初始化语句,不是赋值语句。赋值语句的等号左边是一个早已有定义的变量,赋值语句不会引发复制构造函数的调用
- 函数 F 的参数是类 A 的对象, 当 F 被调用时,类 A 的复制构造函数将被调用(传给形参的是实参的复制)
- 函数的返冋值是类 A 的对象,则函数返冋时,类 A 的复制构造函数被调用
C++析构函数
在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~
符号析构函数(Destructor)也是一种特殊的成员函数,没有返回值, 在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~
符号。
注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
析构函数的作用并不是删除对象,而是在对象被销毁时调用完成一些清理工作。
class A{ private: char *p; public: A(); ~A(); }; A::A(){ p=new char[10]; } A::~A(){ //在对象撤销前对在堆上创建的内存执行释放 delete[] p;//若A类没写析构函数,则在生成A对象后,new出来的内存空间未清除,可 能造成内存泄露 }
在创建一类的对象数组时,对于每一个数组元素,都会执行缺省的构造函数。同样,对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。从前往后构造,从后往前析构。
析构函数的执行时机
- 在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数。
- 在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数。
- new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。
C++ this指针
this
是 C++ 中的一个关键字,一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。
this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。
#include <iostream> using namespace std; class Student{ public: void setname(char *name); void setage(int age); void setscore(float score); void show(); private: char *name; int age; float score; }; void Student::setname(char *name){ this->name = name; } void Student::setage(int age){ this->age = age; } void Student::setscore(float score){ this->score = score; }
成员函数的参数和成员变量重名,只能通过 this 区分。以成员函数setname(char *name)
为例,它的形参是name
,和成员变量name
重名,如果写作name = name;
这样的语句,就是给形参name
赋值,而不是给成员变量name
赋值。而写作this -> name = name;
后,=
左边的name
就是成员变量,右边的name
就是形参。
this 虽然用在类的内部,但是只有在对象被创建以后才会给 this 赋值。
- this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
- 只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用(后续会讲到 static 成员)。
this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值
C++ static静态成员
在C++中,我们可以使用静态成员变量来实现多个对象共享数据的目标。静态成员变量是一种特殊的成员变量,它被关键字static
修饰
static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,static 成员变量,分配一份内存,所有对象使用的都是这份内存中的数据。
示例代码:
#include<iostream> using namespace std; class Student{ public: Student(char *name, int age, float score); private: char *name; int age; float score; private: static int s_total; }; int Student::s_total=0;//static 成员变量必须在类声明的外部初始化 Student::Student(char *name, int age, float score){ this->name=name; this->age=age; this->score=score; this->s_total++; } int main(){ Student::m_total=0;//类访问静态成员变量 Student stu; cout<<stu.m_total<<endl;//对象访问静态成员变量,错误的!!! 访问权限是private对象无法访问 //创建匿名对象 (new Student("小明", 15, 90)) -> show(); }
注意
- 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量
- static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放
- 静态成员变量必须初始化,而且只能在类体外进行。
- 静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存
C++ static静态成员函数
在类中,static 除了可以声明静态成员变量,还可以声明静态成员函数。普通成员函数可以访问所有成员(包括成员变量和成员函数),静态成员函数只能访问静态成员。
静态成员函数没有 this 指针,不知道指向哪个对象,无法访问对象的成员变量,也就是说静态成员函数不能访问普通成员变量,只能访问静态成员变量.
#include<iostream> using namespace std; class Student{ private: static int total_stu; char *name; int age; float score; public: static int get_total_stu();//为了访问 static成员变量,函数也被声明为 static Student(char *name, int age, float score); }; int Student::total_stu=0; Student::Student(char *name, int age, float score){ this->name=name; this->age=age; this->score=score; } int Student::get_total_stu(){ return total_stu; }
C++ const成员函数(常成员函数)
const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。const 成员函数也称为常成员函数。
通常将 get 函数设置为常成员函数。读取成员变量的函数的名字通常以get
开头,后跟成员变量的名字,所以通常将它们称为 get 函数
常成员函数需要在声明和定义的时候在函数头部的结尾加上 const 关键字
- 函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,
- 函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值
#include<iostream> using namespace std; class Student{ private: char *name; int age; float score; public: char *get_name() const; int get_age() const; float get_score const; }; char* Student::get_name() const{ return this->name; } int Student::get_age() const{ return this->age; } float Student::get_score() const{ return this->score; } //必须在成员函数的声明和定义处同时加上 const 关键字
C++ const对象
在 C++ 中,const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)
//栈上创建 const class object(params); class const object(params); //堆上定义 const class *p = new class(params); class const *p = new class(params);
C++友元函数和友元类(C++ friend关键字)
友元(friend)。借助友元(friend),可以使得其他类中的成员函数以及全局范围内的函数访问当前类的 private 成员。
友元函数
在类中声明,但要在前面加 friend 关键字,友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数
友元函数可以访问当前类中的所有成员,包括 public、protected、private 属性的。
一个类友元函数是一个在此类以外定义,在类中声明即可,不同于类的成员函数,友元函数无法直接访问类的成员,必须借助对象。
#include<iostream> using namespace std; class Address;//Student类要用到Address类,要提前声明,不然编译器会报错 class Student{ private: char * name; int age; float score; public: Student(char *name,int age,float score); friend void show(Student &s); friend void Address::show(Address *addr);//只有类正式声明后才能创建对象,但在提前声明之后,可以类名定义指向该类型变量的指针变量或引用变量。 }; Student::Student(char *name, int age, float score){ this->name=name; this->age=age; this->score=score; } void show(Student &s){ cout<<s->name<<s->age<<s->score<<endl; } class Address{ private: char *m_province; //省份 char *m_city; //城市 char *m_district; //区(市区) public: Address(char *province, char *city, char *district); void show(Address *addr); };
友元类
不仅可以将一个函数声明为一个类的“朋友”,还可以将整个类声明为另一个类的“朋友”,这就是友元类。友元类中的所有成员函数都是另外一个类的友元函数
将类 B 声明为类 A 的友元类,那么类 B 中的所有成员函数都是类 A 的友元函数,可以访问类 A 的所有成员,包括 public、protected、private 属性。
#include<iostream> using namespace std; class Address; 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; }; class Address{ public: Address(char *province, char *city, char *district); //声明Student类是Address类的友元类,这样Student类定义的对象可以访问Address类定义的对象中的所有成员; friend class Student; private: char *m_province; char *m_city; char *m_distrcit; }; Student::Student(char *name, int age, float score):m_name(name),m_age(age),m_score(score){} Student::show(Address *addr){//由于在类Address的声明里,声明了Student为Address的友元类,Student的成员函数访问Address类的所有成员。 cout<<addr->m_province<<addr->m_city<<addr->m_score<<endl; } Address::Address(char *province, char *city, char *district):m_province(province),m_city(city),m_district(district){}
注意:
- 友元关系是单向的,A类是B类的友元类(A在B的声明中安插‘奸细’,但B没有),则A可以访问B的所有成员,B却不能访问A的所有成员。
- 友元函数不能继承。A是B的友元,B是C的友元,A不是C的友元
C++ class和struct区别
C++ 中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
class和struct区别:
class
内的成员默认是private,struct
内的成员默认是public的。class
的继承是private的,struct
的继承是public的。class
可以用模板,struct
不能。用
struct
定义类的一个反面教材
#include <iostream> using namespace std; struct Student{ Student(char *name, int age, float score); void show(); char *m_name; int m_age; float m_score; }; Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ } void Student::show(){ cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl; } int main(){ Student stu("小明", 15, 92.5f); stu.show(); Student *pstu = new Student("李华", 16, 96); pstu -> show(); //class默认是private的访问权限,若用类无法访问show成员函数。 return 0; }
C++ string详解与字符串详解
C++ 内置的 string 类处理起字符串很方便,完全可以代替C语言中的字符数组或字符串指针。
代码示例
#include<iostream> #include<string> using namespace std; int main(){ string s1;//只有定义没有初始化,编译器默认指定 "" 空字符串为初始值 string s2="c plus plus";//s2直接赋值 "c plus plus" string s3(5,'s');//调用构造函数构造一个对象"ssssss" string s4=s3;//s4通过s3进行初始化 int s4_len=s4.length(); //返回s4的字符串长度,string ,末尾没有/0,返回值为字符串真实长度。 const char*c_str=s4.c_str();//string 类字符串转换为c风格字符串,返回字符串指针const char* FILE *fp = fopen(c,"rt"); cin>>s1;//输入字符到s1 cout<<s1;//输出s1 s4[1]='d';//修改string字符串的第二个字符为5. }
字符串的拼接,增删改查:
拼接
string 类,使用+
或+=
运算符来直接拼接字符串,+
支持string和string, string+char 数组,string+char单个字符。
#include<iostream> #include<string> using namespace std; int main(){ string s1="first "; string s2="second "; char *s3="third "; char s4[]="fourth "; char ch='@'; string s5=s1+s2; string s6=s1+s3; string s7=s1+s4; string s8=s1+ch; cout<<s5<<endl<<s6<<endl<<s7<<endl<<s8<<endl; return 0; }
增删改查
增:
//原型 string& insert (size_t pos, const string& str); string a1="123456"; a1.insert(5,"aaa");//在a1第五个字符后插入“aaa”
删:
//原型 string& erase (size_t pos = 0, size_t len = npos); string s1, s2, s3; s1 = s2 = s3 = "1234567890"; s2.erase(5);//删除第五个字符后的所有字符 s3.erase(5, 3);//删除第五个字符后3个字符
提取子字符:
//原型 string substr (size_t pos = 0, size_t len = npos) const; string s1 = "first second third"; string s2; s2=s1.substr(6,6); //从s1的第6个字符开始提取6个字符
substr()
和 erase()
参数的处理:
- 如果 pos 越界,会抛出异常;
- 如果 len 越界,会提取从 pos 到字符串结尾处的所有字符。
查:
find()
函数
两种原型为:
size_t find (const string& str, size_t pos = 0) const; size_t find (const char* s, size_t pos = 0) const;
第一个为待查找的子字符串(c---字符串, c++ ---string), 第二个参数是开始查找的位置。
string s1 = "first second third"; string s2 = "second"; s1.find(s2,5);//返回待查找字符串第一次出现的起始下标,若未查找到则返回一个无穷大数值
rfind()
函数:find()
函数类似,只是第二个参数是停止查找位置(在pos处停止)
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@ 笔记小结 @@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
类的成员有成员变量和成员函数两种。
成员函数之间可以互相调用,成员函数内部可以访问成员变量。
私有成员只能在类的成员函数内部访问。默认情况下,class 类的成员是私有的,struct 类的成员是公有的。
可以用“对象名.成员名”、“引用名.成员名”、“对象指针->成员名”的方法访问对象的成员变量或调用成员函数。成员函数被调用时,可以用上述三种方法指定函数是作用在哪个对象上的。
对象所占用的存储空间的大小等于各成员变量所占用的存储空间的大小之和(如果不考虑成员变量对齐问题的话)。
定义类时,如果一个构造函数都不写,则编译器自动生成默认(无参)构造函数和复制构造函数。如果编写了构造函数,则编译器不自动生成默认构造函数。一个类不一定会有默认构造函数,但一定会有复制构造函数。
任何生成对象的语句都要说明对象是用哪个构造函数初始化的。即便定义对象数组,也要对数组中的每个元素如何初始化进行说明。如果不说明,则编译器认为对象是用默认构造函数或参数全部可以省略的构造函数初始化。在这种情况下,如果类没有默认构造函数或参数全部可以省略的构造函数,则编译出错。
对象在消亡时会调用析构函数。
每个对象有各自的一份普通成员变量,但是静态成员变量只有一份,被所有对象所共享。静态成员函数不具体作用于某个对象。即便对象不存在,也可以访问类的静态成员。静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数。
常量对象上面不能执行非常量成员函数,只能执行常量成员函数。
包含成员对象的类叫封闭类。任何能够生成封闭类对象的语句,都要说明对象中包含的成员对象是如何初始化的。如果不说明,则编译器认为成员对象是用默认构造函数或参数全部可以省略的构造函数初始化。
在封闭类的构造函数的初始化列表中可以说明成员对象如何初始化。封闭类对象生成时,先执行成员对象的构造函数,再执行自身的构造函数;封闭类对象消亡时,先执行自身的析构函数,再执行成员对象的析构函数。
const 成员和引用成员必须在构造函数的初始化列表中初始化,此后值不可修改。
友元分为友元函数和友元类。友元关系不能传递。
成员函数中出现的 this 指针,就是指向成员函数所作用的对象的指针。因此,静态成员函数内部不能出现 this 指针。成员函数实际上的参数个数比表面上看到的多一个,多出来的参数就是 this 指针。