《C++ SYNTAX》第3章 函数、指针、引用

(3.1) 函数

//无参无返函数
void func()
{
    cout << "Hello!" << endl;
}
func(); //调用方法举例

//有参无返函数
void func(int a)
{
    cout << "Hello!" << endl;
}
func(10); //调用方法举例

//无参有返函数
int func()
{
    cout << "Hello!" << endl;
    return 10;
}
int a = func(); //调用方法举例

//有参有返函数
int func(int a)
{
    cout << "Hello!" << endl;
    return a;
}
int b = func(a); //调用方法举例

C++函数的参数传递属于值传递,传进函数的实参和函数中的形参的内存地址是不一样的,因此对形参的任何改变都不会影响到实参的值,下面这个swap函数也就达不到交换的目的:

void swap(int a,int b)
{
    int temp = a;
    a = b;
    b = temp;
}

当函数写在主函数后面时,主函数在调用该函数之前需要在主函数前对其进行声明:

#include<iostream>
using namespace std;

int max(int a,int b);

int main()
{
    int a = 1;
    int b = 2;
    cout << "最大值为:" << max(a,b) << endl;
    system("pause");
    return 0;
}

int max(int a,int b)
{
    return a > b ? a : b;
}

接下来讲一下函数的分文件编写(以上面的swap函数为例)。分文件编写的思路是把函数声明函数实现分开,分别写在头文件(.h)和源文件(.cpp)中。

//将函数声明写在头文件swap.h中
void swap(int a,int b);
//将函数实现写在源文件swap.cpp中
#include<iostream>
using namespace std;
void swap(int a,int b)
{
    int temp = a;
    a = b;
    b = temp;
}
//主函数需要包含swap函数的头文件才能调用swap函数
#include"swap.h"
#include<iostream>
using namespace std;
int main()
{
    int a = 1,b = 2;
    swap(a,b);
    cout << "a = " << a << "\tb=" << b << endl;
    system("pause");
    return 0;
}

在C++中,函数的形参列表中的形参是可以有默认值的,这叫做函数的默认参数

int func(int a,int b = 20,int c = 30)
{
    return a + b + c;
}
func(10); //60
func(10,30); //70

如果某个位置已经有了默认参数,从这个位置往后,从左到右都必须有默认值:

int func(int a,int b = 20,int c,int d)
{
    return a + b + c + d;
}
func(10); //不合法
func(10,20,30,40); //合法

函数的声明和实现只能其中一个有默认参数:

#include<iostream>
using namespace std;

int func(int a,int b = 10); //声明

int main()
{
    func(10,20);
    system("pause");
    return 0;
}

int func(int a,int b) //实现
{
    return a + b;
}

在某些时候,函数也可以有一个占位参数

void func(int){}
func(10); //调用时必须传入一个int型的参数
void func(int = 10){} //占位参数还能有默认参数
func(); //调用时可以不传入参数

在函数名相同并且在同一个作用域(全局作用域,即在main函数外)的情况下,函数参数的类型不同或者个数不同或者顺序不同时可以实现函数重载

void func(){}
void func(int a){}
void func(double a){}
func(); //调用第一个函数
func(1); //调用第二个函数
func(1.0); //调用第三个函数
//引用参数作为函数重载
void func(int &a){}
void func(const int &a){}
int a = 10;
func(a); //调用第一个函数
func(10); //调用第二个函数
//函数重载遇到默认参数
void func(int a,int b = 10){}
void func(int a){}
func(10); //这个时候有二义性,会报错,要尽量避免这种情况

(3.2) 指针

编译器在存放一个数据时,是把这个数据放在一个用16进制数表示的内存里面,称为地址,所以想要访问这个数据(或者说这段内存),就必须知道其地址值,但是由于地址值不方便记忆,所以才有了定义变量的概念。
指针可以用来保存某个内存的地址(即指针就是一个地址),提供了一种间接访问内存的方法。不管是什么数据类型,在32位操作系统下,指针都占用4个字节,64位操作系统下都占用8个字节。
空指针:指针变量指向内存中编号为0的空间。可以用于初始化指针变量。空指针指向的内存是不可以访问的(0~255之间的内存编号是被系统占用的)内存。
野指针:指针变量指向非法的内存空间。

#include<iostream>
using namespace std;
int main()
{
    int a = 10;
    int* p; //定义可以指向整型数据的指针变量p
    p = &a; //p指向a这块内存(&是取址符)
    *p = 100; //*作用于指针变量,代表指针指向这块内存的数据(指针的解引用)
    cout << "a = " << a << endl; //a = 100
    system("pause");
    return 0;
}
//常量指针(“常量的指针”,指针的指向可以修改,但是指针指向的值不可以修改)
const int* p = &a;
//指针常量(“指针的常量”,指针的指向不可以修改,但是指针指向的值可以修改)
int* const p = &a;
//常量指针常量(指针的值和指向均不可修改)
const int* const p = &a;

用指针技术访问数组元素时必须找到这个数组的首地址,以此为参照才能访问其他元素:

#include<iostream>
using namespace std;
int main()
{
    int arr[10] = {1,2,3,4,5};
    int* p = arr; //数组名代表数组的首地址
    cout << "第一个元素:" << *p << endl; //输出1
    p++;
    cout << "第二个元素:" << *p << endl; //输出2
    system("pause");
    return 0;
}

我们可以利用指针技术,在定义函数的时候用指针变量定义其形参,使函数参数传递方式变成地址传递,这样swap函数就达到了交换的目的:

void swap(int* a,int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

另外,地址传递(只传递地址,占4或8个字节)相对于值传递(完全拷贝一份实参的数据给型参)会有更高的效率,这一点在结构体作为传递参数的时候更能充分体现。当有了地址传递后,如果我们不希望函数内部对传入的实参进行修改,这时候我们可以有意识地将函数的形参定义成常量指针的形式:

void func(const int* a)
{
    //a= 100; //会报错 
    cout << "a = " << a << endl;
}

最后来看一个封装一个可以用冒泡排序对数组进行升序排序的函数的程序。

#include<iostream>
using namespace std;

void bubbleSort(int* arr,int len)
{
    for (int i = 0;i < len - 1;i++)
    {
        for (int j = i + 1;j < len;j++)
        {
            if (arr[j] < arr[i])
            {
                int temp = arr[j];
                arr[j] = arr[i];
                arr[i] = temp;
            }
        }
    }
}

void printArr(int* arr,int len)
{
    for (int i = 0;i < len;i++)
    {
        cout << arr[i] << "\t";
    }
    cout << endl;
}

int main()
{
    int arr[] = {2,1,3,5,4};
    int len = sizeof(arr)/sizeof(arr[0]);
    bubbleSort(arr,len); //传入的arr代表数组的首地址
    printArr(arr,len);
    system("pause");
    return 0;
}

(3.3) 引用

引用就是给变量起别名,并且别名和原名的数据类型必须的一致。

数据类型 &别名 = 原名;

引用必须初始化,并且初始化后不可以改变,以下语法都是错的:

//引用没有初始化
int &b;
//初始化后不能改变
int &b = a;
int &b = c;

引用的本质是指针常量(指针指向不能改变,所指的值可以改变)。

int a = 10;
int &b = a; //相当于int* const b = &a;
b = 20; //相当于*b = 20;

所以引用传递也可以让swap函数达到交换的目的:

void swap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

和地址传递一样,当有了引用传递后,如果我们不希望函数内部对传入的实参进行修改,这时候我们可以有意识地将函数的形参定义成常量引用的形式:

void func(const int &a)
{
    //a= 100; //会报错 
    cout << "a = " << a << endl;
}

(3.4) 内存分区模型

C++在执行程序时,将内存划分为四个区,在程序运行前有代码区全局区,在程序运行时有栈区堆区,不同区域存放的数据,赋予不同的生命周期。

(1)代码区:存放函数体的二进制代码,由操作系统进行管理,可共享和只读。
(2)全局区:存放全局变量、全局常量、静态变量、字符串常量,由操作系统进行管理。

#include<iostream>
#include<string>
using namespace std;
int global_variable = 1; //全局变量(在函数体外创建的普通变量)
const int global_const = 1; //全局常量(在函数体外创建的常量)
int main()
{
	int local_variable = 1; //局部变量(在函数体内创建的普通变量)
    const int local_const = 1; //局部常量(在函数体内创建的常量)
	string string_variable = "Hello!"; //字符串变量
	static int static_variable = 1; //静态变量
    cout << "全局变量的地址为:" << (int)&global_variable << endl; //4309020
    cout << "全局常量的地址为:" << (int)&global_const << endl; //4298816
    cout << "局部变量的地址为:" << (int)&local_variable << endl; //17823700
    cout << "局部常量的地址为:" << (int)&local_const << endl; //17823688
    cout << "字符串变量的地址为:" << (int)&string_variable << endl; //17823648
    cout << "字符串常量的地址为:" << (int)&"Hello!" << endl; //字符串常量,4308920
    cout << "静态变量的地址为:" << (int)&static_variable << endl; //4309024
    system("pause");
    return 0;
}

(3)栈区:由编译器自动分配和释放,存放函数的参数值、局部变量等。
(4)堆区:由程序员分配和释放(先进后出),若程序员不释放,程序结束时由操作系统回收。

不允许返回局部变量的地址,因为处于栈区的局部变量在函数执行完后会被摧毁(释放地址),这时候再访问它们的地址就是非法操作。若要保留栈区的局部变量的地址不被系统释放,可以用new关键字在堆区开辟内存之后用delete关键字释放该内存。

#include<iostream>
using namespace std;

int* func() //函数的返回值是指针
{
    int* p = new int(10); //在堆区开辟一个存放整型数10的内存
    return p;
}

int main()
{
    int* p = func();
    cout << *p << endl; //输出10
    delete p; //在主函数执行完之前释放掉p在堆区开辟的内存
    system("pause");
    return 0;
}

有时候会在堆区开辟一个数组,它的开辟和释放方法如下:

int* arr = new int[10]; //在堆中开辟一个存放int型长度为10的数组给arr
delete[] arr; //告诉编译器释放的是数组而不是一个数

同理,不允许返回局部变量的引用。可以用static关键字将局部变量变为静态变量存放在全局区,这样在整个程序执行完后该变量才会被释放。staticnew的区别是new是针对内存(地址),static是针对变量。并且如果函数的返回值是引用,那么这个函数调用可以作为左值。

#include<iostream>
using namespace std;

int& func() //函数的返回值是引用
{
    static int a = 10; //静态变量在全局区,程序执行结束才会释放
    return a;
}

int main()
{
    int &b = func(); //用引用类型接收函数返回值
    cout << "b = " << b << endl; //输出10
    func() = 100; //函数做左值
    cout << "b = " << b << endl; //输出100
    system("pause");
    return 0;
}

《 C + +   S Y N T A X 》 系 列 博 客 创 作 参 考 资 料 来 源 《C++\ SYNTAX》系列博客创作参考资料来源 C++ SYNTAX

  1. 《黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难》@黑马程序员.https://www.bilibili.com.

博 客 创 作 : A i d e n   L e e 博客创作:Aiden\ Lee Aiden Lee
特别声明:文章仅供学习参考,转载请注明出处,严禁盗用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值