函数重载
概念
C++中函数名相同,形参列表不同的函数构成重载关系,C语言不支持,所谓形参列表不同:
参数个数不同
参数类型不同
参数顺序不同
void print();
void print(int);
void print(double);
void print(int,double);
void print(double,int);
重载函数的调用
调用重载函数时,根据传递的实参来选择合适的函数调用。
原理:
C++编译函数时不但记录函数名,还会记录参数列表
函数调用时不但匹配函数名,还匹配参数列表
函数重载的意义
对于类似功能,在不同的数据下可以用一个函数名实现,大大简化了上层调用的复杂度。
练习:
使用函数重载实现两个数的加法(add),两个数可以使两个整数,两个双精度浮点数,也可以是一个双精度浮点数和一个整数。
哑元
如果一个形参只有类型,没有形参名,这种参数就叫哑元
int print(int/*哑元*/)
{
//......
}
//哑元的作用就是实现参数没有区别的函数的重载
Train
/*01-函数重载*/
#include <iostream>
using namespace std;
//以下函数构成重载关系
void print()
{
cout<<"print()"<<endl;
}
void print(int a)
{
cout<<"print(int)"<<endl;
}
void print(double a)
{
cout<<"print(double)"<<endl;
}
void print(int a,double b)
{
cout<<"print(int,double)"<<endl;
}
void print(double a,int b)
{
cout<<"print(double,int)"<<endl;
}
int main()
{
print();
print(1);
print(3.15);
print(2,4.6);
print(1.5,7);
//print(12,23);
return 0;
}
/*02-加法函数的重载*/
#include <iostream>
using namespace std;
int add(int a,int b)
{
cout<<"add(int,int)"<<endl;
return a+b;
}
double add(double a,double b)
{
cout<<"add(double,double)"<<endl;
return a+b;
}
double add(int a,double b)
{
cout<<"add(int,double)"<<endl;
return a+b;
}
double add(double a,int b)
{
cout<<"add(double,int)"<<endl;
return a+b;
}
int main()
{
cout<<add(1,2)<<endl;
cout<<add(1.1,2)<<endl;
cout<<add(1.1,2.2)<<endl;
cout<<add(1,2.2)<<endl;
return 0;
}
函数参数的默认值
概念
函数参数的默认值指的是在函数调用时,如果不提供对应的实参,就选择使用参数的默认值,如果传递了实参,就使用实参覆盖默认值。
语法
返回值类型 函数名(形参类型1 形参名1,...,形参类型n 形参名n=默认值)
{
//......
}
1.有默认值的参数必须靠右,一个参数有默认值,其右边的参数必须有默认值
void xxx(int a=10,int b);//错误
2.一个形参有默认值,调用时不提供对应的实参就使用默认值,提供了实参就覆盖默认值
3.使用参数默认值时需要注意不要和函数重载相冲突
int add(int a,int b);
int add(int a,int b,int c=100);//冲突
4.如果有函数声明,参数的默认值必须写到声明中
函数参数的默认值的意义
减少传参的个数,提高函数调用的效率
方便函数的调用
template:
/*04-函数参数的默认值*/
#include <iostream>
using namespace std;
//函数参数的默认值必须 写在声明中
int add(int x=100,int y=200,int z=300);
int main()
{
cout<<add(1,2,3)<<endl;
cout<<add(1,2)<<endl;
cout<<add(1)<<endl;
cout<<add()<<endl;
return 0;
}
int add(int x,int y,int z)
{
return x+y+z;
}
哑元
/*03-哑元*/
#include <iostream>
using namespace std;
void print()
{
cout<<"print()"<<endl;
}
void print(int/*哑元*/)
{
cout<<"print(int)"<<endl;
}
int main()
{
print();
print(1);
return 0;
}
练习:
设计一个打印整数数组的函数,默认打印前5个元素,默认使用逗号作为分隔符,也可以指定打印个数和分隔符
/*05-打印数组*/
#include <iostream>
using namespace std;
void print_arr(int *a,int n=5,char op=',')
{
//打印到倒数第二个
for(int i=0;i<n-1;i++)
cout<<a[i]<<op;
//打印最后一个
cout<<a[n-1]<<endl;
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9};
print_arr(arr);
print_arr(arr,7);
print_arr(arr,3,' ');
print_arr(arr,7,'-');
return 0;
}
int arr[] = {1,2,3,4,5,6,7,8,9};
默认打印 ----- 1,2,3,4,5
打印7个,使用-作为分格符 ------ 1-2-3-4-5-6-7
提示:传三个参数:数组首地址,打印长度和分隔符,打印长度和分隔符使用默认值
面向对象
概念
一切皆对象,一个程序就是一组对象组成的整体,程序的功能由对象之间相互传递消息来实现的。
C++中对象都有类型,同一种类型的对象具有相同的属性和功能。
如何描述对象
抽取描述对象的感兴趣的共同特征
对象的共同特征由属性和功能组成
使用变量描述共同的属性
使用函数描述共同的功能
如何在C++中描述对象的类型
使用结构体(struct)描述
C++对结构体的语法进行了扩展,结构体中不但可以有成员变量,也可以有成员函数。因此结构体可以用来描述对象的类型,在C++中,对象的类型叫类。
//使用结构体描述时间对象
属性:
时
分
秒
功能:
设置时间
获取时间
走秒
....
使用类(class)描述对象的类型
class也可以用来描述对象的类型,语法和struct几乎一致
类中的成员具有访问属性,访问属性分为三类:
访问属性
public --- 公有属性 可以在类外和类内访问该成员
protected --- 保护属性 可以在类内部和子类中访问该成员
private --- 私有属性 只能在类内部访问
类成员的访问属性就是实现封装的基础,封装意思就是私有的数据隐藏,对外的接口公开。struct中的成员的默认访问属性是公开的,class中的成员的默认访问属性是私有的。
在实际开发中,应该显示指定成员的访问属性:
class 类名{
public:
公有成员;
protected:
保护成员;
private:
私有成员
};
//一个类中同个访问属性可以多次出现
//通常把公开的成员写在类的最前面,私有成员写在类的最后
注意:
一般来说,成员变量总是设置为私有属性,如果类外需要访问就提供公开的接口成员函数。
在C++不推荐使用struct,而应该使用class
练习:
设计一个Animal的类,包含name(char [20]),age(int)的私有成员,weight(float)的保护成员,提供show(打印信息),设置name,设置age的对外接口(公开)。
/*08-构造Animal类*/
#include <iostream>
#include <cstring>
using namespace std;
class Animal {
public:
void show() {
cout << name << ":" << age << ":" << weight << endl;
}
//设置name
void set_name(const char *s) {
strcpy(name, s);
}
//设置age
void set_age(int a) {
age = a;
}
//设置体重
void set_weight(float w) {
weight = w;
}
protected:
float weight;
private:
int age;
char name[20];
};
int main() {
Animal an;//调用无参构造函数
an.set_name("小飞飞");
an.set_age(4);
an.set_weight(14.4f);
an.show();
return 0;
}
构造函数
概念
构造函数的作用是初始化对象,处于类内部
构造函数是一个特殊的函数,函数名和类名相同,并且没有返回值
构造函数在创建对象时自动调用一次。
构造函数必须是公有的访问属性。
如果类没有构造函数,编译器会自动生成一个什么也不干的构造函数,如果类中实现了构造函数,编译器就不会做这个工作。
类应该实现构造函数。
构造函数参数的传递
如果构造函数有参数,需要在构造对象铜锅小括号传递实参
Animal an(构造函数的实参);
Animal *pa = new Animal(构造函数的实参);
构造函数的重载和参数默认值
一个类可以有多个构造函数,这些构造函数构成重载关系,在构造对象时选择合适的构造函数去调用。构造函数也可以由参数默认值,但是注意不要和重载冲突。
对象的构造过程
1.系统根据对象大小分配内存空间
2.检查成员变量的类型,如果是基本类型就什么都不做,如果是类类型调用该类的构造函数
初始化参数列表
类中有引用成员,const成员必须在调用构造函数前初始化。
在构造函数的形参列表之后,函数语句体之前,使用初始化参数列表可以在调用构造函数之前对成员进行初始化,语法如下:
class A{
public:
A(...):/*初始化参数列表*/{
//...
}
};
//初始化参数列表
构造函数(形参列表):初始成员1(值1),初始化成员2(值2),...{
函数语句体;
}
//所有的成员都可以使用初始化参数列表来初始化
练习:
为Time类提供构造函数,默认初始化为23:59:55,也可以传入时间初始化,使用初始化参数列表
作业:
实现一个mystring类,用于存储字符串(成员 ===> char *指针,int空间大小),存储使用堆内存,提供构造函数,默认构造空间大小为10的空串。
提供一个打印字符串内容的成员函数
提供一个获取空间长度的成员函数
提供一个修改字符串内容的成员函数(扩容)
/*01-字符串类的作业*/
#include <iostream>
#include <cstring>
using namespace std;
class mystring {
public:
//构造函数
mystring(const char *s = NULL) {
if (!s) {//没有传参数
len = 10;
str = new char[len];
memset(str, 0, sizeof(len));
} else {
len = strlen(s) + 1; //这里为什么要加上去
str = new char[len];
strcpy(str, s);
}
}
//打印字符串
void show() {
cout << str << endl;
}
//获取空间大小
size_t get_len() {
cout << "this(指向构造的对象)" << this << endl;
//使用了该函数构造函数的地址
return len;
}
//修改字符串内容 健壮性
void modify_str(const char *s) {
if (!s) {//为空修改为长度为10的空串
delete[] str;
len = 10;
str = new char[len];
memset(str, 0, sizeof(len));
} else {//非空
if (len < strlen(s) + 1) {//空间不够
//调整空间
delete[] str;
len = strlen(s) + 1;
str = new char[len];
}
strcpy(str, s);
}
}
private:
char *str;//字符串内容首地址
size_t len;//空间大小
};
int main() {
//mystring str1;
mystring str1("hello");
cout << "str1地址:" << &str1 << endl;
str1.show();
cout << str1.get_len() << endl;
str1.modify_str("welcome to GEC!");
str1.show();
cout << str1.get_len() << endl;
mystring str2;
cout << "str2地址:" << &str2 << endl;
cout << str2.get_len() << endl;
return 0;
}