C++ 学习笔记02
一、引用
引用的作用
引用变量是一个别名,是C++ 对 C 的重要扩充。
也就是说,它是某个已存在变量的另一个名字。
一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++之所以增加引用类型, 主要是把它作为函数参数,以扩充函数传递数据的功能。
功能:为一个变量起别名
引用符号:&
使用数据类型加引用符号&
,声明引用。
#include <iostream>
#include <string>
using namespace std;
//作用域限制的是变量的名字。
//既然作用域限制的是变量的名字的使用范围,那么在fun函数中如果能够起main中的a起个
//名字,是不是就可以直接使用main中的变量a了?
//C++的引用就是给变量起别名的。
void fun(int* b);
int main()
{
int a;
fun(&a);
cout<<a<<endl;
return 0;
}
void fun(int* b)
{
*b = 10;
}
基本使用
示例1
#include <iostream>
using namespace std;
//在C和C++中一个符号如果出现在声明的语句中,它一定不是运算符!!!!!!它是表达标识符身份的符号。 * [ ] ( )
//& 在C++中可以出现在声明的语句中,表示标识符是引用。
int main()
{
//这个例子没有任何的实际的逻辑含义。
int a = 10;
int& b = a;//定义引用b,指向a,b是a的别名。
cout<<b<<endl;//10
cout<<a<<endl;//10
cout<<&a<<endl;//地址相同
cout<<&b<<endl;//地址相同
}
1.必须初始化
2.引用不能单独存在,也不能改变指向
3.普通引用不能用常量或者临时值来初始化
示例2
#include<iostream>
using namespace std;
int main()
{
int a=100;
int &ref = a ;//1.引用必须初始化
int b=78;
ref = b; //2.引用不可以改变方向,使用b给变量a赋值
cout<<a<<endl;//78
}
引用作为形参类型
示例3
#include<iostream>
using namespace std;
void fun(int &m)//引用作为参数,m将是实参在fun中的别名
{
m = 100;//在fun中使用m就是在使用实参本身,所以这里是给main函数中的a赋值。
}
int main()
{
int a=90;
fun(a);
cout<<a<<endl;//100
}
练习1
编写函数swap实现main中两个整数交换 要求:形参用引用
void swap(int& a, int& b)
{
a = a^b;
b = a^b;
a = a^b;
}
int main()
{
int m = 10, n = 20;
swap(m, n);
cout<<m<<" "<<n<<endl;
return 0;
}
引用做返回值
引用作为返回值,是返回变量本身,而不是一个临时的值,要保证变量的生命周期足够长。
示例4
#include<iostream>
using namespace std;
int& max(int& x)
{
x = 90;
return x;
}
int main()
{
int a = 3;
//如果返回值不是引用,是不能像下面这么赋值的!!
max(a) = 8 ; //因为返回值是引用,不是一个临时的值,所以可以直接对返回值赋值
cout<<a<<endl;//返回值是a本身,所以上一行是对a赋值,所以这里输出8
}
1.引用必须初始化。
2.引用不能单独存在。
3.引用也不能改变指向。
4.普通引用不能用常量或者临时值来初始化.
const 修饰的引用:常引用
做形参
#include<iostream>
using namespace std;
void fun(const int &y)
{
y = 90;//语法错误,引用是const修饰的,不能够用过引用去改变实参的值
}
int main()
{
int a = 100;
fun(a);
return 0;
}
判断下面的几种定义引用对错
int& b; //错误,引用不能单独存在,必须初始化
int& b = 10; //错误,普通的引用不能作为常量的引用
const int& b = 10; //对, 常引用可以指向常量
示例5
判断对错
#include <iostream>
using namespace std;
int main()
{
int a = 10;
const int& b = a;//正确
b = 20; //错误,不能通过常引用改变变量的值
a = 30; //正确
cout<<a<<endl;
}
示例6
常量初始化问题
#include <iostream>
using namespace std;
//常引用可以给常量起名字,局部使用的产量使用常引用起名字。
int main()
{
int a = 10;
int& c = a + 2;//错误,普通引用不能引用常量
const int& c = a + 2;//正确
cout<<c<<endl;
}
引用必须初始化。
引用不能单独存在。
引用也不能改变指向。
普通引用不能指向常量与临时量。
引用与指针的区别
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用,引用必须连接到一块合法的内存。指针可以是空指针。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。 指针可以在任何时间被赋值。
二、动态内存分配
变量可能存在的空间
不同的内存空间堆变量的生命周期管理不同。
- 堆: 自定义生命周期。 只申请不释放堆空间内存,内存泄漏。
- 栈: 作用域结束,变量就释放。
- 静态: 程序结束才释放。
动态空间的申请和释放
c中函数 malloc-free
#include<cstdlib>
malloc申请的内存空间是没有类型的void*
不会初始化
c++中 关键字 new-delete
new申请的内存空间是有类型的 给内存初始化
int *p=(int*)malloc(23);
int *p = new int;
变量
示例7
#include<iostream>
using namespace std;
int main()
{
int *p = new int;//在堆空间申请一段int类型的内存,使用指针变量p指向这段内存
*p = 123;
cout<<*p<<endl;
delete p;//释放堆空间的内存 new只能用delete释放 malloc只能用free释放
p = NULL;//如果释放之后,还要使用指针p,那么一定要在释放后把p置NULL
int* p1 = new int(200);//在堆空间申请int类型的内存并且初始化
cout<<*p1<<endl;//200
delete p1;
p1 = NULL;
}
数组
示例8
#include<iostream>
using namespace std;
int main()
{
int* p = new int[10];//在堆空间申请一个int类型的10个元素的数组
for(int i = 0 ; i < 10; i++)
{
p[i] = i; //*(p+i) = i;
}
for(int i = 0 ; i < 10; i++)
cout<<p[i]<<" ";
cout<<endl;
delete[] p;//释放数组要加[]
p = NULL;
}
练习2
从键盘输入学生分数int 4 统计出最高分 最低分 平均分 要求:空间动态开辟new
#include <iostream>
using namespace std;
int main()
{
int* arr = new int[4];//动态创建一个数组
for(int i = 0;i < 4;i++)
{
cin>>arr[i];
}
int max = arr[0];
int min = arr[0];
int sum = arr[0];
for(int i = 1;i < 4;i++)
{
if(arr[i] > max)
{
max = arr[i];
}
else if(arr[i] < min)
{
min = arr[i];
}
sum += arr[i];
}
cout<<max<<" "<<min<<" "<<sum/4.0f<<endl;
delete[] arr;//释放堆空间的内存
return 0;
}
三、函数延伸
重载(overload)
overload它不是关键字,它只是重载的英文翻译
c++允许创建多个名称相同的函数(同一个函数名定义多个函数 一物多用)
要求: 形参列表必须不同(形参类型或者形参个数,类型 个数 至少有一种不同),返回值无所谓
注意: 如果形参列表相同 但返回值不同 是不可以重载
形参个数不同
示例9
//两个show函数互为重载关系
void show(float a,float b)
{
cout<<a*b<<endl;
}
void show(char s)
{
cout<<"char...."<<s<<endl;
}
int main()
{
show(1.f, 2.f);//编译器根据实参列表匹配对应的重载函数
show(‘f’);
return 0;
}
形参类型不同
示例10
#include <iostream>
using namespace std;
//两个show函数,参数列表的类型不一样,这里返回值类型也不一样,对重载没有影响!
int show(int a,int b)
{
cout<<"int..."<<a*b<<endl;
return a*b;
}
void show(float a,float b)
{
cout<<a*b<<endl;
}
void show(char s)
{
cout<<"char...."<<s<<endl;
}
int main()
{
int result = show(5,6);//执行show(int,int)
show(10.0f,3.3f);//执行show(float, float)
show('a');//执行show(char)
}
练习3
重载dev函数,分别进行两个int类型和两个float类型的数相除
#include <iostream>
using namespace std;
int dev(int a, int b)
{
return a/b;
}
float dev(float a, float b)
{
return a/b;
}
int main()
{
cout<<dev(10, 3)<<endl;
cout<<dev(10.f, 3.f)<<endl;
return 0;
}
注意
C++对于字面值常数的默认类型规定
如 3.14默认为double 类型
double可以隐式转化为float或是int
那么,定义如下两个函数,
int fun(int n,int m);
float fun(float n,float m);
fun(10.0,2.5)
就有歧义了
修改为fun(10.0f,2.5f)
或者fun(10.0f,2.5)
都行
函数默认参数
默认参数: 函数调用时 形参从实参得到值,可以给形参一个默认值 形参值就不必从实参取值了
注意:形参列表只能从右侧开始有默认值
//下面代码是错的,因为形参列表只能从右侧开始有默认值
void fun(int a = 10, int b = 20, int c)
{
}
c没有默认值,a和b也别想有默认值
b没有默认值,a不可以有默认值,c可以有默认值
a没有默认值,b和c可以有默认值
示例11
#include <iostream>
using namespace std;
void fun(int a= 70,int b =80,int c=90)//参数1 2 3都有默认参数值
{
cout<<"a: "<<a<<"b: "<<b<<"c: "<<c<<endl;
}
int main()
{
//实参列表,从形参左边开始赋值
fun(10,20,30);//10 20 30
fun(10,20);//10 20 90
fun(10);//10 80 90
fun();//70 80 90
}
示例12
//带声明的
#include <iostream>
using namespace std;
//当函数的声明和定义分开写的时候,只能在声明的部分写默认参数,这是我们绝大部分的//使用场景。
void fun(int a= 70,int b =80,int c = 90);
int main()
{
fun(10,20,30);
fun(10,20);
fun(10);
fun();
}
void fun(int a,int b,int c )
{
cout<<"a: "<<a<<"b: "<<b<<"c: "<<c<<endl;
}
练习4
定义函数,判断一个年份是否是闰年,声明中给默认年份2016
bool isLeapYear(int year);
#include <iostream>
using namespace std;
bool isLeapYear(int year = 2016);//函数声明写默认参数
int main()
{
cout<<isLeapYear()<<endl;//不传参使用默认值2016
cout<<isLeapYear(2021)<<endl;//传参判断2021
return 0;
}
bool isLeapYear(int year)//函数的声明写了默认参数,那么函数体就不能再写默认参数了
{
return year%4==0&&year%100!=0 || year%400==0;
}
示例13
- 形参是引用、非引用时:
void func(int &x);
void func(int x);
不属于重载。 - 形参是 const 引用、非const 引用时:
void func(const int &x);
void func(int &x);
属于重载。 - 形参是普通指针、指向常量的指针时:
void func(int * p) ;
void func(int const * p);
属于重载。 - 形参是常指针、指向常量的指针时:
void func(int const *p);
void func(int *const p);
属于重载。 - 形参是普通指针、指向常量的常指针时:
void func(int * p);
void func(int const * const p);
属于重载。 - 形参使用了 typedef、没使用了 typedef时:
typedef unsigned int UINT;
void func(UINT b);
void func(unsigned int x);
不属于重载。 - 重载出现访问不明确的错误
void fun(int a = 10, int b = 20, int c = 30) { cout<<"1"<<endl; } void fun(int a) { cout<<"2"<<endl; } int main() { fun(10);//出现访问不明确的错误 return 0; }
注意:我们分辨重载,主要看能不能通过实参列表来区分!!
四、类的初步
定义类
class 类名
{
public:公有
};
创建实例对象
类名 对象名;
调用类成员
对象名.属性;
对象名.函数();
示例14
栈空间创建对象并调用成员函数
#include<iostream>
#include<string>
using namespace std;
class Student//类名 Student
{
public:
string name; //成员变量
int age; //成员变量
void setName(string n)//成员函数
{
name = n;//name是成员变量
}
void initInfo(string n,int a)//成员函数
{
name = n;//同一个对象里的成员函数,使用是同一个成员变量
age = a;
}
void print()//成员函数
{
cout<<name<<" "<<age<<endl;
}
};
int main()
{
Student stu;//在栈空间创建了一个Student类型的对象 stu
stu.name = "小明";//直接对成员变量赋值
stu.age = 10;
cout<<stu.name<<endl;//直接输出成员变量的值
stu.setName("小红");//通过成员函数对成员变量赋值
stu.print();//通过成员函数输出成员变量的值
stu.initInfo("tom",26);
stu.print();
}
练习5
定义一个类 Array,定义二维数组成员,
重载函数print()
,分别实现常规打印数组元素和以一定格式打印数组。
数组数据初始化 init();
向二维数组中装入数据,数组的赋值,怎么赋值都行
print();
输出效果如下:
0 1 2 3
1 2 3 4
2 3 4 5
print(char space);
输出效果如下:
0#1#2#3
1#2#3#4
2#3#4#5
#include <iostream>
using namespace std;
class Array
{
public:
int arr[3][4];
void init()
{
for(int i = 0;i < 3;i++)
{
for(int j = 0;j < 4;j++)
{
arr[i][j] = i+j;
}
}
}
void print()
{
print(' ');
}
void print(char space)
{
for(int i = 0;i < 3;i++)
{
for(int j = 0;j < 4;j++)
{
cout<<arr[i][j]<<(j==3?'\0':space);
}
cout<<endl;
}
}
};
int main()
{
Array array;
array.init();
array.print();
array.print('#');
return 0;
}
示例15
堆空间创建对象
int main()
{
Student* p = new Student();//在堆空间创建一个Student类型的对象
p->setName("小明");
p->setAge(18);
p->print();
delete p;//删除对象
}
2 条评论
滴!学生卡!打卡时间:下午1:43:11,请上车的乘客系好安全带~
写得好好哟,我要给你生猴子!