目录
构造函数和析构函数的类型:构造函数可以后形参,但没有返回值;析构函数没有形参,没有返回值
一、new和delete
二、引用
1.引用的基本语法:
数据类型 &别名 = 原名
int a = 10;
int &b = a;
引用后上述的b和a可以理解为是一样的。例如:
# include <iostream>
using namespace std;
int main ()
{
int a = 10;
int &b = a;
cout << a << endl;
cout << b << endl;
b = 20;
cout << a << endl;
cout << b << endl;
return 0;
}
结果:
10
10
20
20
2.引用的注意事项:
1.引用必须初始化
2.引用在初始化后就不能再改变引用
# include <iostream>
using namespace std;
int main ()
{
int a = 10;
int &b;//错误,引用必须初始化
int &c = a;
int d = 20;
c = d;//不报错,但这不是将c引用d,而是d的值赋值给c原来的引用,即a=20=c;
return 0;
}
3.引用作为函数的参数:
# include <iostream>
using namespace std;
void f(int &a, int &b)//交换两个数的值
{
int c;
c = a;
a = b;
b = c;
}
int main ()
{
int a = 10;
int b = 20;
f(a,b);
cout << a << b << endl;
return 0;
}
结果:
20 10
4.引用作为函数的返回值:
1.不能返回局部变量的引用
# include <iostream>
using namespace std;
int & f()
{
int a = 10;
return a;
}
int main ()
{
int &b = f();
cout << b << endl;
cout << b << endl;
return 0;
}
结果:
10
1515108752
结果只有第一次是正确的,这是因为编译器暂时保留了栈区里面的局部变量,第二次就错误了。
2.返回值为引用的函数可以作为左值
# include <iostream>
using namespace std;
int & f()
{
static int a = 10;//设置为static类型是为了保证在全局区,从而下面可以多次调用
return a;
}
int main ()
{
int &b = f();
cout << b << endl;
cout << b << endl;
f() = 20;//作为左值,此时相当于a = 20
cout << b << endl;
return 0;
}
结果:
10
10
20
5.引用的本质:
引用的本质就是常量指针
# include <iostream>
using namespace std;
void f(int &a)//编译器转化为int* const a = &b
{
a = 100;//转化为*a = 100
}
int main ()
{
int b = 10;
int & c = b;//转化为int* const c = &b
c = 30;//转化为*c = 30
cout << b << endl;
cout << c << endl;
return 0;
}
6.常量引用:
1.不能通过常量引用来修改值
# include <iostream>
using namespace std;
int main ()
{
int b = 10;
const int &a = b;
//a = 20;错误,不能通过常量引用来修改值
b = 20;
cout << a << endl;
cout << a << endl;
return 0;
}
2.其他
int &a = 10;//错误
const int &b = 10;//正确
三、函数的注意事项
1.函数的默认参数
函数中如果某个位置有默认参数后,那么后面的所有参数都必须是默认参数
如果函数声明有默认参数,那么函数定义时就不能由默认参数
void f(int a = 10, int b);//函数声明
void f(int a, int b)//函数定义时不能写成void f(int a = 10, int b)
{
cout << a << endl;
}
2.函数的占位参数
函数的占位参数也有默认值时函数的占位参数不必补齐,但没有默认值时必须补齐
函数的占位参数也可以有默认参数
# include <iostream>
using namespace std;
void f(int a, int)//占位参数
{
cout << "aaa" << endl;
}
void g(int a, int = 10)//占位参数可以有默认值
{
cout << "aaa" << endl;
}
int main ()
{
f(10,20);//调用时必须补齐占位参数
g(10);//调用有默认值的占位参数时可以不必补齐占位参数
return 0;
}
3.函数重载
引用作为函数重载
void f(int & a)
{
cout<<"aaa"<<endl;
}
void f(const int & a)//可以作为重载的条件
{
cout<<"aaa"<<endl;
}
int main ()
{
int b = 10;
f(b);//调用上面的那个
f(10);//调用下面的那个
return 0;
}
四、构造函数和析构函数
构造函数和析构函数的类型:构造函数可以后形参,但没有返回值;析构函数没有形参,没有返回值
定义构造函数和析构函数
class student
{
public:
student()//无参构造函数
{
cout<<"无参构造函数"<<endl;
}
student(int a)//有参构造函数
{
cout<<"有参构造函数"<<endl;
}
student(const student& stu)//拷贝构造函数
{
cout<<"拷贝构造函数"<<endl;
}
~student()//析构函数
{
cout<<"析构函数"<<endl;
}
}
调用构造函数和析构函数
class student
{
public:
student()//无参构造函数
{
cout<<"无参构造函数"<<endl;
}
student(int a)//有参构造函数
{
cout<<"有参构造函数"<<endl;
age=a;
}
student(const student& stu)//拷贝构造函数
{
cout<<"拷贝构造函数"<<endl;
age=stu.age;
}
~student()//析构函数
{
cout<<"析构函数"<<endl;
}
int age;
}
int main ()
{
//括号法调用
student stu1;//调用无参构造
student stu1(10);//调用有参构造
student stu1(stu2);//调用拷贝构造
//显示法
student stu1;//调用无参构造
student stu1=student(10);//调用拷贝构造
student stu1=student(stu2);//调用拷贝构造
//隐式转换法
student stu1;//调用无参构造
student stu1=10;//调用拷贝构造,等价于 student stu1=student(10)
student stu1=stu2;//调用拷贝构造,等价于 student stu1=student(stu2)
//注意:不能这样写
student stu1();//这样不是默认构造,编译器会认为是函数声明
return 0;
}
注意事项:
1.匿名对象
# include <iostream>
using namespace std;
class student
{
public:
int a;
student(int b)
{
a=b;
}
student(const student & stu)
{
a=stu.a;
}
student()
{
cout<<"默认构造"<<endl;
}
};
int main ()
{
student stu1;
stu1.a=10;
student stu2=student(stu1);//student(stu1)即为匿名对象,相当于stu2来作为他的名字
cout<<stu2.a<<endl;
return 0;
}
2.不要利用拷贝构造函数初始化匿名对象
class student
{
public:
int a;
student(const student & stu)
{
a=stu.a;
}
student()
{
cout<<"默认构造"<<endl;
}
};
student stu1;
student(stu1);//错误,编译器会认为是student(stu1)==student stu1
五、拷贝构造函数
1.拷贝函数的调用时机
(1)使用一个已经创建完毕的对象来初始化一个新的对象
(2)值传递的方式给函数参数传值
(3)以值方式返回局部对象
# include <iostream>
using namespace std;
class student
{
public:
student()
{
cout<<"默认构造函数"<<endl;
}
student(int age)
{
cout<<"有参构造函数"<<endl;
m_age=age;
}
student(const student & stu)
{
m_age=stu.m_age;
cout<<"拷贝构造函数"<<endl;
}
~student()
{
cout<<"析构函数"<<endl;
}
int m_age;
};
//第二种:值传递的方式给函数参数传值
void g(student stu)
{
cout<<stu.m_age<<endl;
}
void f()
{
student stu(10);
g(stu);
}
//第三种:以值方式返回局部对象
student h()
{
student stu(10);
return stu;//此时返回的是stu的一个拷贝,而不是stu
}
int main(int argc, char const *argv[])
{
student stu=h();
cout<<stu.m_age<<endl;
f();
student stu1(10);
//第一种:使用一个已经创建完毕的对象来初始化一个新的对象
student stu2(stu1);
cout<<stu2.m_age<<endl;
return 0;
}
2.构造函数的调用规则
规则:(1)默认情况下,C++给编译器提供3个函数:默认构造函数(函数体为空)、默认析构函数(函数体为空)、默认拷贝构造函数。
(2)如果用户提供无参构造函数,则C++不再提供默认构造函数(函数体为空),但还提供默认析构函数(函数体为空)和默认拷贝构造函数
如果用户提供有参构造函数,则C++不再提供默认构造函数(函数体为空),但还提供默认拷贝构造函数
如果用户提供拷贝构造函数,则C++不再提供其他构造函数
3.深拷贝与浅拷贝
C++提供的拷贝函数是浅拷贝;自己为了清理堆区的内存而定义的拷贝函数为深拷贝。
# include <iostream>
using namespace std;
class student
{
public:
int a;
int *c;
student(int b,int d)
{
a=b;
c=new int(d);
cout<<"有参构造"<<endl;
}
student(const student & stu)//深拷贝
{
a=stu.a;
c=new int(*stu.c);
/*
浅拷贝只是简单的赋值,会导致两个对象的指针指向同一块内存,
从而在析构时出现重复释放内存的现象,所以需要自己用深拷贝来解决。
自己再为拷贝对象重新开辟一段新的内存,从而使两者指向不同的内存
*/
cout<<"拷贝构造"<<endl;
}
student()
{
cout<<"默认构造"<<endl;
}
~student()//只要类中出现了指针,就需要用下面的方法清理内存
{
if(c!=NULL)//清理在堆区的内存
{
delete c;
c=NULL;
}
cout<<"析构函数"<<endl;
}
};
int main ()
{
student stu1(19,10);
student stu2(stu1);
return 0;
}
六.初始化列表
# include <iostream>
using namespace std;
class student
{
public:
student(int a,int b,int c):my_a(a),my_b(b),my_c(c)//初始化列表
{
cout<<"初始化列表"<<endl;
}
int my_a;
int my_b;
int my_c;
};
int main ()
{
student stu(10,20,30);
cout<<stu.my_a<<" "<<stu.my_b<<" "<<stu.my_c<<endl;
return 0;
}
七、类对象作为类成员
注意:作为成员的那个一定要写在上面,也就是下面的class test02要写在class test01的上面。
先调用作为成员的类对象,在调用外面的类
# include <iostream>
# include <string>
using namespace std;
class test02
{
public:
test02(string name)
{
cout<<"test02的构造函数"<<endl;
name02=name;
}
~test02()
{
cout<<"test01的析构函数"<<endl;
}
string name02;
};
class test01
{
public:
test01(string name1,string name2):name01(name1),test(name2)
{
cout<<"test01的构造函数"<<endl;
}
~test01()
{
cout<<"test01的构造函数"<<endl;
}
string name01;
test02 test;
};
void test03 ()
{
test01 my_test("zhangpeng","HUAWEI");
cout<<"Name:"<<my_test.name01<<" Phone:"<<my_test.test.name02<<endl;
}
int main(int argc, char const *argv[])
{
test03();
return 0;
}
结果:
test02的构造函数
test01的构造函数
Name:zhangpeng Phone:HUAWEI
test01的构造函数
test01的析构函数
八、静态变量与静态成员函数
1.静态成员函数
# include <iostream>
# include <string>
using namespace std;
class student
{
public:
static void f()
{
a=20;
//b=50;静态成员函数只能调用静态变量
cout<<"静态成员函数"<<endl;
}
static int a;
int b=30;
};
int student::a=10;//静态变量类内定义,类外初始化
int main(int argc, char const *argv[])
{
//调用静态成员函数
//第一种方法
student stu1;
stu1.f();
//第二种方法
student::f();//因为静态成员函数是共有的,不属于哪一个对象
return 0;
}
2.静态变量
# include <iostream>
using namespace std;
class student
{
public:
static int a;
int b=30;
};
int student::a=10;//静态变量类内定义,类外初始化
int main(int argc, char const *argv[])
{
//调用静态变量的方法
//第一种:通过对象调用
student stu;
stu.a=20;
//第二种:通过类来调用。因为静态变量属于类,不属于某一个对象
student::a=20;
cout<<stu.a<<endl;
return 0;
}
九、成员变量与成员函数分开存储
注意:1.非静态变量属于对象。静态变量、静态函数、非静态函数都属于类,不属于某一对象。
2.空对象的字节大小
# include <iostream>
using namespace std;
class student
{
};
int main ()
{
student stu;
cout<<sizeof(stu)<<endl;
return 0;
}
结果:空对象编译器会分配1个字节来占一个位置
1
# include <iostream>
using namespace std;
class student
{
int a;
};
int main ()
{
student stu;
cout<<sizeof(stu)<<endl;
return 0;
}
结果:不是5,随然空对象分配1个字节,但只是占位,有东西后就不在占位
4
十、this指针(本质是一个常指针)
this指针内部是:student * const this,指针指向的值不能变
class student
{
public:
void f()
{
this=NULL;//错误,指针指向的值不能变
}
};
student stu;
stu.f();//错误,指针指向的值不能变
哪一个对象调用类的成员函数,this就指向这个对象。
# include <iostream>
using namespace std;
class student
{
public:
/*
this的第一个用途:用来区分形参和成员变量名字相同的情况
*/
student(int age)
{
this->age=age;
}
/*
this的第二个用途:返回对象本身,从而可以实现链式编程
注意:下面不能返回student,如果返回student则每次都会返回一个新的对象
使用student&每次都返回同一个对象
*/
student& AgeAdd(const student& stu1)
{
this->age+=stu1.age;
return *this;
}
int age;
};
int main(int argc, char const *argv[])
{
student stu(10);
student stu1(20);
stu1.AgeAdd(stu).AgeAdd(stu).AgeAdd(stu);
cout<<stu1.age<<endl;
return 0;
}
十一、空指针
# include <iostream>
using namespace std;
class student
{
public:
int age;
void f()
{
cout<<"调用f函数"<<endl;
}
void g()
{
cout<<"调用g函数"<<endl;
age=10;
}
};
void test01()
{
student *stu=NULL;
stu->f();
/*
上面正确,下面错误;f()函数没有用成员变量,并且成员函数只属于类
所以f()函数正确;g()函数使用了成员变量,成员变量属于对象,
空指针没有对象,所以错了
*/
//stu->g();
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
十二、常对象与常函数
# include <iostream>
using namespace std;
class student
{
public:
/*
常函数只能调用普通成员变量,但是不能修改普通成员变量,
常函数可以调用修改mutable类型变量
常函数不能调用普通函数
常函数可以调用常函数
*/
/*
this指针内部是:student * const this,指针指向的对象不能变
而常函数则是const student * const this 指针指向的对象不能变
并且指向对象的值也不能变
*/
void f() const//常函数
{
cout<<age<<endl;
weight=20;
g();
//h();//错误
}
void g() const
{
}
void h()
{
}
int age=10;
mutable int weight;
};
void test01()
{
/*
常对象可以调用常函数
常对象可以调用修改mutable类型的成员变量
常对象可以调用普通成员变量,但不能修改
常函数不能调用普通函数
*/
const student stu1;//常对象
cout<<stu1.age<<endl;
//stu1.age=100;//错误
student stu;
stu.f();
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
十三、友元
1.全局函数的友元
# include <iostream>
# include <string>
using namespace std;
class student
{
friend void f(const student & stu);//全局成员函数的元友
public:
student(string name1,string name2)
{
settingroom=name1;
sleeproom=name2;
}
string settingroom;
private:
string sleeproom;
};
void f(const student & stu)
{
cout<<"正在访问"<<stu.settingroom<<endl;
cout<<"正在访问"<<stu.sleeproom<<endl;//访问私有成员
}
void test01()
{
student stu("客厅","卧室");
f(stu);
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
2.类的对象作为友元
# include <iostream>
# include <string>
using namespace std;
class building
{
friend class student;//类的对象作为友元
public:
string sitting_room;
building();
private:
string bed_room;
};
class student
{
public:
building* build;
void visit();
student();
};
student::student()
{
build=new building;
}
void student::visit()
{
cout<<"访问"<<build->sitting_room<<endl;
cout<<"访问"<<build->bed_room<<endl;//访问私有成员变量
}
building::building()
{
sitting_room="客厅";
bed_room="卧室";
}
void test01()
{
student stu;
stu.visit();
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
3.类的成员函数作为友元
# include <iostream>
# include <string>
using namespace std;
//必须加上这一句,否则下面student中用building时不认识
class building;//必须加上这一句,否则下面student中用building时不认识
class student
{
public:
student();
building * build;
void visit();
};
class building
{
//注意:在这里必须保证上面有定义student类
friend void student::visit();//成员函数作为友元
public:
building();
string sitting_room;
private:
string bed_room;
};
building::building()
{
sitting_room="客厅";
bed_room="卧室";
}
student::student()
{
build=new building;
}
void student::visit()
{
cout<<"参观"<<build->sitting_room<<endl;
cout<<"参观"<<build->bed_room<<endl;//访问私有成员变量
}
void test01()
{
student stu;
stu.visit();
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
注意:用什么东西,在用之前都必须要么已经前面定义过了,要么在前面先声明后面定义。否则编译器会报错。
十四、继承
1.继承的基本语法
class A:继承方式(public/private/protected) B
2.继承的规则
(1)public
class A:public B
{
};
A继承B, A只能访问B中的public和private成员,不能访问B中的private成员;并且B中的public成员到A中是public成员,protected成员到A中是protected成员。
(2)protected
class A:protected B
{
};
A继承B, A只能访问B中的public和private成员,不能访问B中的private成员;并且B中的public成员到A中是protected成员,protected成员到A中是protected成员。
(3)private
class A:private B
{
};
A继承B, A只能访问B中的public和private成员,不能访问B中的private成员;并且B中的public成员到A中是private成员,protected成员到A中是private成员。
3.继承的内容
规则:
子类可以调用修改父类中的public成员函数和变量、protected普通成员函数和变量、public静态成员函数和变量、protected静态成员函数和变量。
子类不可以调用修改父类的private函数和变量。
父类的友元也可以访问子类的private函数和变量。
子类真正继承并且占用内存的只有非静态成员变量。(因为静态变量、静态函数、非静态函数都属于类,不属于某一对象)
# include <iostream>
using namespace std;
class student
{
friend void f4();
public:
int a=1;
static int a1;
static void f1()
{
cout<<"调用父类静态函数"<<endl;
}
void f2()
{
cout<<"调用父类普通函数"<<endl;
}
private:
int b=3;
protected:
int c=4;
};
int student::a1=2;
class student1:public student
{
public:
int d=5;
void f4()
{
cout<<"调用父类普通成员变量"<<a<<endl;
cout<<"调用父类静态成员变量"<<a1<<endl;
//cout<<"调用父类静态成员变量"<<b<<endl;//不能访问父类私有变量
cout<<"调用父类保护成员变量"<<c<<endl;
}
void f3()
{
f1();//调用父类普通成员函数
f2();//调用父类静态成员函数
cout<<"调用子类普通函数"<<endl;
}
};
void f4()
{
student1 stu1;//子类也可以继承父类友元函数
stu1.f4();
stu1.f3();
stu1.f1();
stu1.f2();
}
int main(int argc, char const *argv[])
{
f4();
return 0;
}
结果:
调用父类普通成员变量1
调用父类静态成员变量2
调用父类保护成员变量4
调用父类静态函数
调用父类普通函数
调用子类普通函数
调用父类静态函数
调用父类普通函数
4.继承的顺序
# include <iostream>
using namespace std;
class student
{
public:
student()
{
cout<<"父类的构造函数"<<endl;
}
~student()
{
cout<<"父类的析构函数"<<endl;
}
};
class student1:public student
{
public:
student1()
{
cout<<"子类的构造函数"<<endl;
}
~student1()
{
cout<<"子类的析构函数"<<endl;
}
};
int main(int argc, char const *argv[])
{
student1 stu1;
return 0;
}
结果:
父类的构造函数
子类的构造函数
子类的析构函数
父类的析构函数
5.继承同名非静态成员的处理方法
子类对象可以直接访问子类中的同名成员
子类对象加作用域可以访问父类中的同名成员
当子类与父类拥有同名的成员函数时,子类会隐藏父类中的同名成员函数,加作用域可以访问到父类中的同名函数
# include <iostream>
using namespace std;
class student
{
public:
int a=10;
void fun()
{
cout<<"父类的函数"<<endl;
}
};
class student1:public student
{
public:
int a=20;
void fun()
{
cout<<"子类的函数"<<endl;
}
};
void test01()
{
student1 stu;
cout<<"子类的成员变量 "<<stu.a<<endl;//访问子类的成员变量
cout<<"父类的成员变量 "<<stu.student::a<<endl;//访问父类的成员变量
stu.fun();//访问子类的成员函数
stu.student::fun();//访问父类的成员函数
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
结果:
子类的成员变量 20
父类的成员变量 10
子类的函数
父类的函数
6.继承同名静态成员的处理方法
# include <iostream>
using namespace std;
class student
{
public:
static int a;
static void fun()
{
cout<<"父类的静态函数"<<endl;
}
};
int student::a=10;
class student1:public student
{
public:
static int a;
static void fun()
{
cout<<"子类的静态函数"<<endl;
}
};
int student1::a=20;
void test01()
{
//第一种方式:通过对象访问
student1 stu;
cout<<"子类的静态成员变量 "<<stu.a<<endl;//访问子类的静态成员变量
cout<<"父类的静态成员变量 "<<stu.student::a<<endl;//访问父类的静态成员变量
stu.fun();//访问子类的静态成员函数
stu.student::fun();//访问父类的静态成员函数
//第二种方式:通过类直接访问
cout<<"子类的静态成员变量 "<<student1::a<<endl;//访问子类的静态成员变量
cout<<"父类的静态成员变量 "<<student::a<<endl;//访问父类的静态成员变量
cout<<"父类的静态成员变量 "<<student1::student::a<<endl;//访问父类的静态成员变量
student1::fun();//访问子类的静态成员函数
student::fun();//访问父类的静态成员函数
student1::student::fun();//访问父类的静态成员函数
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
结果:
子类的静态成员变量 20
父类的静态成员变量 10
子类的静态函数
父类的静态函数
子类的静态成员变量 20
父类的静态成员变量 10
父类的静态成员变量 10
子类的静态函数
父类的静态函数
父类的静态函数
7.多继承的调用方式
# include <iostream>
using namespace std;
class student1
{
public:
student1()
{
a1=100;
a2=200;
}
int a1;
int a2;
};
class student2
{
public:
student2()
{
a1=300;
a3=400;
}
int a1;
int a3;
};
class student:public student1,public student2
{
public:
int a1=600;
int a5=500;
};
void test01()
{
student stu;
cout<<"调用父类1中的不同名变量 "<<stu.a2<<endl;
cout<<"调用父类1中的同名变量"<<stu.student1::a1<<endl;
cout<<"调用父类2中的不同名变量"<<stu.a3<<endl;
cout<<"调用父类2中的同名变量"<<stu.student2::a1<<endl;
cout<<"调用子类中的变量"<<stu.a5<<endl;
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
结果:
调用父类1中的不同名变量 200
调用父类1中的同名变量100
调用父类2中的不同名变量400
调用父类2中的同名变量300
调用子类中的变量500
8.菱形继承
# include <iostream>
using namespace std;
class student
{
public:
int a1;
int a2;
};
//虚继承,使student3只继承了一组a1和a2
class student1:virtual public student {};
class student2:virtual public student {};
class student3:public student1,public student2 {};
void test01()
{
student3 stu;
stu.a1=100;
stu.student1::a1=200;
stu.student2::a1=300;
cout<<"a1的值为:"<<stu.a1<<endl;
//因为是虚继承,只继承了一组,所以循环改变后是最后一次的值
stu.a2=100;
stu.student1::a2=200;
stu.student2::a2=300;
cout<<"a2的值为:"<<stu.a2<<endl;
}
int main(int argc, char const *argv[])
{
test01();
return 0;
}
结果:
a1的值为:300
a2的值为:300