C++类和对象
C++在非对象方面的常用新特性
函数重载
函数重载不能只有返回值不同,形参也需要有区别函数重载不能只有返回值不同,形参也需要有区别。
example
/* 函数重载 */
//void funca(int a,int b); 是错误的
int funca(int a,int b)
{
return a+b;
}
float funca(float a,float b)
{
return a-b;
}
double funca(double a,double b)
{
return a*b;
}
void 函数重载()
{
cout<<"函数重载例子:\n"
<<"整型相加funca(1,2):"<<funca(1,2)<<"\n"
<<"单浮点相减funca((float(0.1)),(float(0.2))):"<<funca((float(0.1)),(float(0.2)))<<"\n"
<<"双浮点相乘funca(0.1,10.1):"<<funca(0.1,10.1)
<<"\n-----------------------------------------------"<<endl;
}
有默认参数的函数
1.默认参数必须是函数参数表中最右边的参数
2.从最右边的参数开始必须得右参数,要么就全没有,从右->左。
3.需要默认参数的函数在第一次出现的就应该非默认值,否则错误。
example
/*
error
int volume(int a = 0,int b,int c);
int volume(int a = 0,int b = 0,int c);
int volume(int a = 0,int b,int c = 0);
error
*/
//从最右边的参数开始必须得有参数,要么就全没有,从右往左
void volume(int a,float b,double c);
int volume(int a,int b,int c = 0);
float volume(float a,float b = 0,float c = 0);
double volume(double a = 0,double b = 0,double c = 0);
变量的引用
建立引用的作用是为一个变量起另一个名字,以便在需要时可以方便、间接地引用该变量,对一个变量的引用的所有操作,实际上都是对其所代表的变量的操作。引用有时候占用空间,有时候不占用空间。在引用对象作为函数参数时,不会产生新对象。
example
void quote(void)
{
int a = 10,b = 0;
int &c = a; //c为a的引用,a c 代表同一个存储单元
int* d = &a;
//&c = b;错误写法,不能赋值
cout<<"引用基础例子:\n"
"a的地址:"<<&a<<"\n"
<<"c的地址:"<<&c<<"\n"
<<"a的值:"<<a<<"\n"
<<"c的值:"<<c<<"\n"
<<"b的值:"<<b
<<endl;
swap(a,b);
cout<<"swap(int& a,int& b):\n"
<<"a的值:"<<a<<"\n"
<<"b的值:"<<b<<"\n"
<<"-----------------------------------------------"<<endl;
}
将引用作为函数参数
C语言中,给函数的形参传实参,传递是单向的,相当于实参给形参赋值,在函数内部使用已经赋值的形参。在C++中使用形参定义为引用,此时参数传递是双向的。
example
void swap(int& a,int& b)
{
int p = a;
a = b;
b = p;
}
常引用
用const对引用限定,不允许改变引用的值,但是可以改变原变量的值,通常用作函数形参,保证形参的值不被改变。常引用作为参数传递不会产生拷贝,可以提高运行效率。
example
int a = 6;
const int& b = a;
b = 10; //错误写法,常量不可改变
a = 10; //正确,a为整形变量
const int& c = a+3; //正确,可以用表达式对常引用初始化,系统将生成一个临时变量,用于存储表达式的值,引用是临时变量的别名。用户不能访问临时变量。
int& d = a + 3; //错误,对非常引用只能用变量初始化
动态分配和释放内存的运算符new和delete
example
new int; //分配一个存放整数的空间,返回一个指向整形数据的指针
new int(6); //分配一个存放整数的空间并且初始化为6,返回一个指向整形数据的指针
new char[10]; //分配一个存放字符数组的空间,该数组偶10个元素,返回一个指向字符数据的指针
delete 指针变量; //释放单个数据的存储空间
delete []指针变量; //释放数组存储空间或指针存储空间
void 动态分配(void)
{
int* p = new int(6);
char*cp = new char[10];
cp[0] = '1';
cout<<"动态分配例子:\n"
<<*p<<"\n"
<<cp[0]
<<endl;
delete p;
p = nullptr;
delete []cp;
cp = nullptr;
cout<<"释放空间:\n"
<<p<<"\n"
<<"-----------------------------------------------"
<<endl;
}
//释放内存后将指针指向空,避免产生野指针。
布尔类型
bool变量包含两种取值: ture or false (1[非0] or 0)
由结构到类的发展
从结构到类的演化
C++中用struct定义类,默认访问权限是public;用class定义类,默认访问权限是private。public、private、protected是面向对象的封装性。
example
sturct point
{
private:
double x;
double y;
public:
point(double a = 0,double b = 0) //构造函数
{x = a; y = b;}
void set(double a,double b)
{
x = a;
y = b;
}
void show(void)
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
};
void 结构体类(void)
{
point a(6,6);
cout<<"结构体类例子:\n"
<<"构造函数:";
a.show();
a.set(9,9);
cout<<"设置函数:";
a.show();
cout<<"-----------------------------------------------"
<<endl;
}
C++类的声明与对象的定义
面向对象的程序设计方法的关键要素是抽象、封装、继承和多态性。
类的申明
example
class point
{
private:
double x;
double y;
public:
point(double a=0,double b=0);
void set(double a=0,double b=0);
void show(void);
};
//体外成员函数
返回值类型 类名::成员函数名(形参表)
{
成员函数的函数体;
}
point::point(double a,double b)
{
x = a;
y = b;
}
void point::set(double a,double b)
{
x = a;
y = b;
}
void point::show(void)
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
int main()
{
point a(1,0);
a.show();
point* b = &a;
b->set(6,6);
b->show();
return 0;
}
构造函数
构造函数的定义
一个类中可以有多个构造函数(函数重载),声明格式为:
类名(形参…);
在类中没有定义构造函数,会自动生成一个构造函数,为空不执行初始化操作,是默认构造函数。
类名(){}
用参数初始化表对数据成员进行初始化和使用默认参数
类名(形参1,形参2…):数据成员1(参数1),数据成员2(参数2)…{}
example
class time
{
private:
int h;
int m;
int s;
public:
time(int a=0,int b=0,int c=0):h(a),m(b),s(c){}
};
析构函数
~类名(){} 对象声明周期结束的时候(撤销对象之前),自动执行析构函数,可以在析构函数内撤销对象所占用的内存。若在程序中没有主动释放,则会在return 0之后释放,强烈建议主动释放,在嵌入式中程序一般不会执行结束。
构造函数和析构函数的一般执行顺序
构造函数顺序:
析构函数顺序:
自动释放对象的情况下:
构造函数–对象先创建则先执行。
构造函数–对象先创建则后执行。类似栈,先进后出。
复制构造函数
类名(const 类名 & 源对象);
复制构造函数也是构造函数的一个重载。
错误示例:
当默认复制构造函数执行后( 对象2(对象1) ),若原对象1中有指针指向空间,则该对象2与对象1的中的指针指向同一块内存,如果析构函数中执行了delete []p的操作,则其中任意一个对象释放后,另一个对象在释放时会发生内存错误。
定义复制构造函数
class string
{
private:
char *strValue;
public:
string(char* s = "")
{
if(s == NULL) s="";
strValue = new char[strlen(s)+1]; //strlen不会把'\0'算入,字符串都是以'\0'为结尾,所以+1
strcpy(strValue,s);
}
string(const string& copy) //复制构造函数
{
strValue = new char[strlen(copy.strValue)+1]; //将源对象的字符串拿来分配空间大小
strcpy(strValue,copu.strValue); //复制对象复制源对象的字符串
}
~string(){delete []strValue;}
void show(){cout<<strVlaue<<endl;}
};
void func1(string s);
void func2(const string& s);
int main()
{
string s1("test");
string s2(s1); //调用复制构造函数
func1(s1); //此时func函数中的s对象会执行复制构造函数,在传参时,会产生中间变量出现复制现象。并且在该函数结束后会执行中间对象的撤销,析构函数也会执行。
func2(s1); //func2是通过常引用来传参数,不会产生中间变量,没有新对象的产生,直接使用源对象的数据。
return 0;
}
用const保护数据
常对象成员
常对象成员包括常数据成员和常成员函数。
常数据成员
常数据成员的值不能被改变。常数据成员只能通过构造函数的参数初始化表进行初始化。
example
class a
{
private:
const int c;
int d;
public:
a(int b):c(b) //通过初始化参数来初始化赋值,之后不能改变
{
d = 100;
cout<<"创建对象"<<endl;
}
a(const a& copy):c(copy.c),d(copy.d)
{
cout <<"c:"<<c<<endl;
cout <<"d:"<<d<<endl;
cout<<"复制构造函数"<<endl;
}
~a(){cout<<"清除对象"<<endl;}
};
常成员函数
如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改它们,例如只用与输出数据。在声明函数和定义函数时都要const关键字,在调用时不加const。
常成员函数内不能修改成员变量的值,不得调用其他非常成员函数的函数。(read-only)
example
class a
{
private:
const int c = 0;
int d;
public:
a(int b):c(b){d = 100;cout<<"创建对象"<<endl;}
a(const a& copy):c(copy.c),d(copy.d)
{
cout <<"c:"<<c<<endl;
cout <<"d:"<<d<<endl;
cout<<"复制构造函数"<<endl;
}
int mul() const;
int add()
{
return c+d;
}
void show() const
{
//d = 10; //不可修改变量
//add(); //不可调用非常成员函数
mul();
}
~a(){cout<<"清除对象"<<endl;}
};
int a::mul() const
{
return c*d;
}
常对象
类名 const 对象名
orconst 类名 对象名
常对象不能改变常对象中数据成员的值,只能调用常成员函数。
函数名与形参表完全相同的常成员函数与非常成员函数可以进行重载,非常对象优先调用非常成员函数。
若常成员函数一定要修改变量的值,则需要声明为
mutable
class mytest
{
private:
mutable int count = 0; //用mutable申明后,可以在常成员函数中修改值
public:
void show() const;
};
void mytest::show() const
{
cout<<++count<<endl;
}
对象的常引用
使用对象的常引用,在调用函数时作为形参,不必简历实参的拷贝,可以提高程序运行效率,还能保护数据不被修改。
友元
友元(friend)可以访问类的保护成员和私有成员。[是对CPP封装性的破坏,一般不使用,除非能提高效率。]
友元函数
如果在某个类以外的地方定义一个函数(函数可以是不属于任何类的函数,也可以是类的成员函数),在对这个类体中用friend对此函数声明,则该函数为这个类的友元函数。
friend 返回值类型 函数名(形参表);
class mytest
{
private:
mutable int count = 0;
int a;
public:
void show() const;
friend void fshow(mytest& copy);
void func()
{
a = 10;
}
};
void mytest::show() const
{
cout<<++count<<endl;
}
void fshow(mytest& copy)
{
copy.a = 10;
cout<<copy.a<<endl;
}
int main()
{
mytest c;
fshow(c); //通过此方法可以改变c中的private变量
return 0;
}
将另一个类的成员函数声明为一个类的友元函数
friend 返回值类型 类名::函数名(形参表);
class mytest
{
private:
mutable int count = 0;
int a;
public:
void show2() const; //声明
};
class mytest2
{
private:
int x;
int y;
public:
friend void mytest::show2() const; //声明
};
void mytest::show2() const //定义
{
cout<<a<<endl;
}
C++允许对类做提前声明,就像先声明函数一样。在类定义之前不能直接用类去定义对象,只能用类先去定义一个对象的指针或对象的引用,因为定义对象需要直接分配空间。
友元类
friend class 类名
例如:B类声明为A类的友元类。这时友元类B中的所有成员函数都是A类的友元函数,可以访问A类中的所有成员。
可以前置声明了类后再定义为友元类。
class mytest;
class mytest2;
class mytest2
{
private:
int x;
int y;
public:
friend class mytest; //声明mytest为mytest2的友元类
};
class mytest
{
private:
mytest2* p1;
public:
mytest(const mytest& copy)
{
p1 = new mytest2(copy);
}
void show() const;
};
void mytest::show() const
{
cout<<p1->x<<","<<p1->y<<endl;
}
静态成员
静态数据成员
声明:
static 类型名 数据成员名
访问:
类名::静态数据成员名
通用性
对象名.静态数据成员名
静态数据成员被该类的所有对象共享,无论建立多少个该类的对象,都只有一个静态数据成员的存储空间。
在类内可以直接访问所有的静态数据成员,类的静态数据成员必须在类外进行初始化:
类型名 类名::静态数据成员名 = 初始值;
如果只声明了类而未定义对象,将为静态数据成员分配存储空间,但对类的非静态数据成员不分配存储空间,只有在定义对象后,才为对象的非静态数据成员分配空间。
class a
{
public:
static int b;
};
int a::b = 0;
int main()
{
cout<<a::b<<endl;
return 0;
}
静态成员函数
静态成员函数只属于类。在类外调用一个静态成员函数不需要指明对象。
static 返回值类型 函数名(形参名);
调用:
类名::静态成员函数名(实参表);
this指针
this指针属于对象,当非静态成员函数通过某个对象被调用时,this指向这个对象。静态成员函数是类的一部分,不是对象的一部分,不含this指针。
类的成员函数的代码与类的对象是分开存放的,成员函数的代码在内存中只有一份拷贝,当类的不同对象调用成员函数时,为使成员函数知道是对类的哪个对象进行操作,C++为非静态成员函数提供了叫this的隐含指针,当创建一个类的对象时,系统就会自动生成一个this指针,并且将this指针的值初始化为该对象的地址。
example
class test
{
private:
double x;
double y;
public:
void set(double x,double y) //形参名和成员变量名相同时,会优先使用形参
{
this->x = x;
this->y = y;
}
};
int main()
{
test a;
a.set(1,2); //此时this指向对象a
return 0;
}