引用
引用符号:&
使用方式:
- 类型 &引用变量=目标变量
- 例如:int &r=n
作用
- 使两个变量数值始终相等
- 引用变量或目标改变,令一变量也跟着改变,始终保持相等
int main(){
int n=4;
int &r=n;
cout<<r<<" "<<n<<endl;//4 4
r=5;
cout<<r<<" "<<n<<endl;//5 5
n=6;
cout<<r<<" "<<n<<endl;//6 6
return 0;
}
- 引用更多使用在函数上,来改变主函数变量值
void swap1(int &x,int &y){
int t=x;
x=y;
y=t;
}
int main(){
int a=1,b=2;
cout<<a<<" "<<b<<endl;//1 2
swap(a,b);
cout<<a<<" "<<b<<endl;//2 1
return 0;
}
注意事项
- 定义引用时一定要初始化成引用某个变量,不可单独定义引用
- 初始化后,不可改变引用的目标变量
- 引用只可引用变量,不可引用常量和表达式
拓展问题
- 引用一个引用?
答:三者联动
#include<iostream>
using namespace std;
int main(){
int n=1;
int &x=n;
int &y=x;
cout<<n<<" "<<x<<" "<<y<<endl;//1 1 1
n=2;
cout<<n<<" "<<x<<" "<<y<<endl;//2 2 2
x=3;
cout<<n<<" "<<x<<" "<<y<<endl;//3 3 3
y=4;
cout<<n<<" "<<x<<" "<<y<<endl;//4 4 4
return 0;
}
- 同时引用一个变量?
答:三者联动
#include<iostream>
using namespace std;
int main(){
int n=1;
int &x=n;
int &y=n;
cout<<n<<" "<<x<<" "<<y<<endl;//1 1 1
n=2;
cout<<n<<" "<<x<<" "<<y<<endl;//2 2 2
x=3;
cout<<n<<" "<<x<<" "<<y<<endl;//3 3 3
y=4;
cout<<n<<" "<<x<<" "<<y<<endl;//4 4 4
return 0;
}
常const
使用方式:const 类型 变量
定义常量
- 常量值不可改变
const int N=1e5+10;
const string user="cwj";
定义常量指针
- 不可通过常量指针去修改其指向的内容
- 但可以修改常量指针的指向
int main(){
int n,m;
const int *p=&n;
*p=1;//报错
n=1;//ok
p=&m;//指针的指向可以改变
return 0;
}
- 函数参数为常量指针时,可避免函数内部不小心改变参数指针所指向的内容
void input(const char *p){
cout<<*p;//正常使用可以
strcpy(p,"cwj");//不可改变,报错
}
定义常引用
- 不可通过常引用修改其引用的变量
- 但可以通过修改引用的变量来改变引用的值
int main(){
int n=1;
const int &r=n;
n=2;//可以
cout<<n<<" "<<x<<endl;//2 2
r=2;//报错
return 0;
}
动态内存分配
1.new动态内存分配一个变量
- P=new T
- T为任意类型名,p是类型为*T的指针
- 动态分配出一片大小为size(T)字节的内存空间,并将该内存空间的首地址赋值给P
int main(){
int *P=new int;
*P=100;
return 0;
}
2.new动态内存分配一个数组
- P=new T[N]
- T为任意类型名,p是类型为*T的指针
- 动态分配出一片大小为size(T)*N字节的内存空间,并将该内存空间的首地址赋值给P
int main(){
int *P=new int[100];
P[0]=0;
P[1]=1;
.....
P[99]=99;
P[100]=100;//越界
return 0;
}
3.delete释放一个变量
- delete 指针
- 被删除的指针必须是new出来的
int main(){
int *P=new int;
*P=1;
delete P;
return 0;
}
4.delete释放一个数组
- delete []指针
- 被删除的指针必须是new出来的
int main(){
int *P=new int[100];
P[0]=1;
delete []P;
return 0;
}
内联函数 inline
前言
-
由于函数调用是由时间开销的,如果函数本身只有几句语句,执行非常快,但函数被反复调用很多次,相比之下,函数调用产生的开销甚至更大。
-
为减小函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,会将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
使用方式
- 在函数前多加一个关键字inline
inline int max(int a,int b){
if(a>b)return a;
else return b;
}
函数重载或函数缺省
什么是函数重载
- 一个或多个函数,名字相同,但是参数个数或参数类型不相同,就叫做函数重载。
- c++支持函数重载,使得函数命名变得简单。
- 重载时,编译器会自动根据调用语句中的实参个数和实参类型判断应该调用哪个函数
使用示例
int max(int a,int b);
int max(double a,double b);
int max(int a,int b,int c);
什么是函数缺省
- 定义函数时,可以让函数最右边的连续个参数有缺省值,当调用函数时,若相应位置不写参数,参数值就是缺省值
- 函数参数缺省的目的在于提高程序的可扩充性
- 例如:一个写好的函数要添加新的参数,而原先调用该函数的语句,未必需要使用新增加的参数,那么就可以使用缺省参数来避免对原先函数调用语句的修改
使用示例
int fun(int a,int b,int c=3){
return a*b^c;
}
int main(){
cout<<fun(3,4)<<endl;
cout<<fun(3,4,0)<<endl;
return 0;
}
类与对象
类成员的可访问范围声明
- 类的定义中,可用以下访问范围关键字来说明类成员可被访问的范围
- private:私有成员,只能在成员函数内访问
- public:公有成员,可以在任何地方访问
- protected:保护成员,先不管它
类成员的可访问范围
- 在类的成员函数内部,能够访问:当前对象的全部属性和函数;也能够访问同类其他对象的全部属性和函数
- 在类的成员函数外的地方,只能访问该类对象的共有成员
定义一个类
- 如果某成员前面没有关键字,则默认认为私有成员
class 类的名字{
private:
私有变量和函数
public:
公有变量和函数
protected:
保护属性和函数
}
符号=
- 等号=的效果和结构体的等号的效果一样。
- 即:使左边那个对象的每一个字节都和右边对象的每一个字节所存储的内容相同
类的成员函数
成员函数的定义和声明
- 成员函数可在类内部直接声明和定义
- 成员函数也可在类内部声明,在类外面定义,在类外部定义时要注意前面添加 “函数返回类型 类名::函数名”,来表示是哪个类的成员函数
- 例如:void Complex::input(){}
class Complex{
private:
double real,imag;
public:
void set(double r,double i){
real=r;
imag=i;
}
void input();
};
void Complex::input(){
cout<<real<<" "<<imag<<endl;
}
构造函数
- 成员函数的一种,名字和类名相同,可以有参数,但不能有返回值
- 作用:对对象进行初始化,在对象生成的一瞬间自动被调用,如给成员变量赋初值
- 无构造函数时,系统自动生成默认的无参数构造函数。有构造函数时,系统不再生成无参构造函数
- 一个类可以有多个构造函数,满足重载和缺省
- ps:构造函数最好是public的,private构造函数不能直接用来初始化对象
示例1:默认的构造函数
class Complex{
private:
double real,imag;
public:
void set(double r,double i){
real=r;
imag=i;
}
};
int main(){
Complex c1;//默认构造函数被调用
Complex *p=new Complex; //默认构造函数被调用
return 0;
}
示例2:自己定义的构造函数
class Complex{
private:
double real,imag;
public:
void set(double r,double i){
real=r;
imag=i;
}
Complex(double r,double i=0){
real=r;
imag=i;
}
Complex(double r,double i,int key=1){
real=r*key;
imag=i*key;
}
};
int main(){
Complex c1;//错误,缺少构造函数的参数
Complex *p=new Complex; //,错误,缺少构造函数的参数
Complex c2(1);//正确,第二个参数有对应的缺省参数
Complex c3(2,3);//错误,不清楚是用第一个构造函数还是用第二个
Complex c4(4,5,6);//正确
return 0;
}
- 对于一个类数组,其有多个对象,且每个对象都会执行一次对应的构造函数
- 运用的方式非常多样和灵活,不多解释,用代码简单展示一下
示例3:构造函数在数组中的使用
class Text{
private:
int x;
public:
Text(){
cout<<"use the first constructor"<<endl;
}
Text(int n){
cout<<"use the second constructor"<<endl;
}
Text(int a,int b){
cout<<"use the third constructor"<<endl;
}
};
int main(){
Text c1;//first
Text c2(1);//second
Text c3(1,2);//third
cout<<endl;
Text c4[3]={
0,{
1,2}};//second,third,first
Text c5[3]={
Text(0),Text(1,2),};//second,third,first
cout<<endl;
Text* c6=new Text[3];//first,first,first
Text* c7[3]={
new Text(1),NULL,new Text(2,3)};//second,中间无,third(只生成两个对象)
cout<<endl;
return 0;
}
复制构造函数
- 成员函数中的一种
- 形如:X::X(X&)或X::X(const X &),X为类名
- 只有一个参数,即对同类对象的引用
- 作用:用一个对象对另一个对象进行初始化
- 如果没有定义复制构造函数,那么编译器自动生成默认的复制构造函数。默认的复制构造函数完成完整复制功能
- 复制构造函数发生作用的三种情况
- 1.当用一个对象去初始化另一个对象的时候
- 2.如果某函数有一个参数是类A的对象的时候,那么该函数被调用时,类A的复制构造函数被使用,且其复制构造函数的参数是它自己。
- 3.如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用(以下没有这个例子,以后再学,懒得理解)
- 注意:对象的赋值并不导致复制构造函数被调用
class Csample {
private:
int a, b;
public:
Csample() {
a = 3;
b = 4;
}
Csample(const Csample&c) {
a = c.a * c.b;
b = c.a + c.b;
}
void Inputab() {
cout << "a=" << a <<" " << "b=" << b << endl;
}
void Func(Csample c) {
cout << "a=" << c.a << " " << "b=" << c.b << endl;
}
};
int main{
Csample c1;
Csample c2(c1);//c2调用复制构造函数用c1初始化c2
c1.Inputab();//c1由构造函数初始化,则 a=3 b=4
c2.Inputab();//c2由复制构造函数用c1初始化c2,则 a=12 b=7
Csample c3;
c3.Func(c2);//函数变量中有类A的对象时,类A的复制构造函数将被调用,输出84 19,但在主函数中c2的值并没有被改变
c3.Inputab();//c3由构造函数初始化,则 a=3 b=4
c2.Inputab();//a=12 b=7
}
- 为避免情况2导致复制构造函数大量被应用,可使用const常量引用参数
void Func(const Csample c) {
}
析构函数
- 成员函数的一种
- 名字与类名相同,但在前面多加个’~’,没有参数和返回值
- 一个类最多只能有一个析构函数
- 作用:析构函数对象消完时其析构函数自动被调用,用于对象消完的善后作用,比如释放分配的内存
- 如果定义的类没有写析构函数,则编译器自动生成一个缺省析构函数,缺省析构函数什么都不做;如果定义了析构函数,则编译器不生成缺省析构函数
析构函数起作用的三种情况如下
- 1.main函数结束,main函数内的所有元素全部消完,每个元素的析构函数都会被调用
class A {
public:
~A() {
cout<<"我无了"<<endl;
}
};
int main() {
A s;
A p[3];
cout<<"end main"<<endl;
//在end main后面输出四个我无了
return 0;
}
- 2.delete运算导致析构函数被调用
- 注意delete数组时一定要用delete[]
class A {
public:
~A() {
cout<<"我无了"<<endl;
}
};
int main() {
A* s=new A;
A* p=new A[3];
A* q=new A[3];
delete s;
cout<<endl;
//一个对象消完,析构函数被调用一次,输出一次我无了
delete[] p;
cout<<endl;
//三个对象消完,析构函数被调用三次,输出三次我无了
delete q;
cout<<endl<<"end";
//只delete一个不合法,在dev不报错,但直接卡死
return 0;
}
- 3.析构函数在对象作为函数返回值返回后被调用
class Csample {
public:
~Csample(){
cout<<"我无了"<<endl;
}
};
Csample fun(Csample t){
return t;
}
int main() {
Csample x;
Csample y=fun(x);
//end main之后输出三次我无了,两次是x,y的消完,
//一次是作为函数返回值返回后被调用产生的一次消完
return 0;
}
this指针
什么是this指针
- 最初c++程序都是先翻译成c程序再进行编译的
- c++中的类的对象转化为c中的结构体变量,
- 修改对象的值相当于传结构体的地址到函数中,此时该函数需要有一个指针,作为指向结构体的指针,这个指针就是this指针。
- 即c相对于c++,其函数的形参会多一个this指针
- this指针始终指向对象自身
为什么要有this指针,this指针是必要的吗
- this指针的作用就是指向成员函数所作用的对象
- 非静态成员函数中,可直接使用this来代表指向该函数作用的对象的指针
- 当类的成员函数需要返回一个对象本身时,必须要通过 *this
String& String::operator=(const String& s) {
//重载=,实现对象赋值
if(this==&s)return *this;//避免自己等于自己
if(str!=NULL)delete[] str;//清理空间
str=new char[sizeof(s)+1];
strcpy(str,s.str);
size=s.size;
return *this;
}
静态成员
静态成员变量
- 定义前面加了static关键字的成员变量
- 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享
- s i z e o f sizeof sizeof不会计算静态成员变量的占用字节
静态成员函数
- 定义前面加了static关键字的成员变量
- 静态成员函数不能访问非静态成员变量,也不能调用非静态成员函数
运算符重载
基本概念
- 运算符重载,就是对已有的运算符赋予多重的含义,使同一运算符作用于不同类型的数据时,导致不同类型的行为。
- 运算符重载的目的:拓展运算
- 符的适用访问,使原本只能对基本数据进行操作的运算符变成能够作用于对象>
- 同一个运算符,对不同的操作数,所发生的行为不同
运算符重载的形式
- 运算符重载的实质是函数重载
- 可以重载为普通函数,也可以重载为成员函数
- 把含运算符
基本形式