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
}

示例1和示例2没有实际意义,只是为了说明语法。

引用作为形参类型

示例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;
}

总结:
引用必须初始化。
引用不能单独存在。
引用也不能改变指向。
普通引用不能指向常量与临时量。

引用与指针的区别

引用很容易与指针混淆,它们之间有三个主要的不同:

  1. 不存在空引用,引用必须连接到一块合法的内存。指针可以是空指针。
  2. 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  3. 引用必须在创建时被初始化。 指针可以在任何时间被赋值。

二、动态内存分配

变量可能存在的空间

不同的内存空间堆变量的生命周期管理不同。

  1. 堆: 自定义生命周期。 只申请不释放堆空间内存,内存泄漏。
  2. 栈: 作用域结束,变量就释放。
  3. 静态: 程序结束才释放。

动态空间的申请和释放

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

  1. 形参是引用、非引用时:
    void func(int &x); void func(int x);不属于重载。
  2. 形参是 const 引用、非const 引用时:
    void func(const int &x); void func(int &x);属于重载。
  3. 形参是普通指针、指向常量的指针时:
    void func(int * p) ; void func(int const * p);属于重载。
  4. 形参是常指针、指向常量的指针时:
    void func(int const *p); void func(int *const p);属于重载。
  5. 形参是普通指针、指向常量的常指针时:
    void func(int * p); void func(int const * const p);属于重载。
  6. 形参使用了 typedef、没使用了 typedef时:
    typedef unsigned int UINT;
    void func(UINT b); void func(unsigned int x);不属于重载。
  7. 重载出现访问不明确的错误
    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;//删除对象
}
End

本文标题:C++ 学习笔记02

本文链接:http://chisato.cn/index.php/archives/98/

除非另有说明,本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

声明:转载请注明文章来源。

最后修改:2021 年 10 月 06 日 03 : 55 PM
如果觉得我的文章对你有用,请随意赞赏