函数又称子程序,是语句块的集合,可以实现特定的功能
结构体是一种数据结构,可以将多个相关字段包装成一个整体使用

函数 (function)

 

函数的定义

返回值类型 函数名 (参数列表)
{
    函数体语句;
    
    return 表达式;
}

形参(parameter)和实参(argument)

The terms parameter and argument may have different meanings in different programming languages. Sometimes they are used interchangeably, and the context is used to distinguish the meaning.

The term parameter (sometimes called formal parameter) is often used to refer to the variable as found in the function definition, while argument (sometimes called actual parameter) refers to the actual input supplied at function call.


传值调用、传址调用、引用调用

  • 引用注意事项:
    引用定义时需要初始化,初始化后不能改变指向
  • 区别:

    • 传值
      实参和形参是两个不同的地址空间
    • 传址
      实参是变量的地址,形参是一个指针,对形参的操作就是对地址所对应的变量的操作
    void fun(int* a){...;}
    
    fun(&b);
    • 传引用

    形参是实参的别名,对形参的操作就是对实参的操作

    void fun(int& a){...;}
    
    fun(b);
  • 引用的本质: 常指针

    //发现是引用,转换为 int* const ref = &a;
    void func(int& ref){
        ref = 100;  // ref是引用,转换成 *ref = 100;
    }
    int main(){
        int a = 10;
        
        //转换为 int* const ref = &a;
        int& ref = a;
        ref = 20;  //  转换为 *ref = 20;
        
        func(a);
        
        return 0;
    }

引用做函数返回值

  • 注意: 不要返回局部变量的引用

    int& fun(){
    /*以下是错误的
    int a = 10;
    return a;
    */
    static int a = 10;  //静态变量
    return a;
    }
  • 函数的调用可以作为左值

    int vals[] = {1,2,3,4,5};
    
    int& setValues(int i){
        return vals[i];  //返回第i个元素的引用
    }
    
    int main() {
        //返回引用的函数可以做左值
        setValues(1) = 7;
        setValues(3) = 9;  //vals: 1,7,3,9,5
    }

常量引用

用来修饰形参,防止误操作

//打印数据函数
void showValue(const int& val){
    //val = 1000; //不允许修改
    cout<<"val = "<<val<<endl;
}
//int& ref = 10;  //引用本身需要一个合法的内存空间,因此这行错误

//加入const就可以了,实际上编译器 int temp = 10; const int& ref = temp;
const int& ref = 10;

内联函数 (inline function)

优点:解决了宏(Macro)的缺陷,又带来了宏的优点,以空间换时间

关键字:inline (函数的声明和实现前要同时有inline)

  • 宏函数缺陷1:需要加括号保证运算完整

    #define MYADD(x,y) ((x)+(y))
    
    void test01(){int ret = MYADD(10,20) * 20;}
  • 宏函数缺陷2:即使加了括号,有些情况依然与预期效果不符

    #define MYCOMPARE(a,b) (((a)<(b))?(a):(b))
    
    void test02()
    {
        int a(10), b(20);
        //宏函数展开时,有两次++a
        int ret = MYCOMPARE(++a,b);  //ret的值为12
    }
  • 内联函数

    inline int myCompare(int a, int b)
    {
        return a<b?a:b;
    }

类内部的内联函数

任何在类内部定义的函数 自动成为 内联函数

编译器(compiler)的限制

  • 不能存在任何形式的循环语句
  • 不能存在过多的条件判断语句
  • 函数体不能过于庞大
  • 不能对函数进行取址操作

内联仅仅是给编译器的一个建议,编译器不一定会接受这种建议,而没有声明为内联函数的,编译器也可能自动将此函数做内联编译。一个好的编译器将会内联小的、简单的函数


函数的默认参数 (default argument)

//如果某个位置已经有了默认参数,那么这个位置以后都必须有默认参数
int add(int a, int b=0, int c=0){
    return a+b+c;
}
//要么函数的声明有默认参数,要么函数的实现有默认参数

int main(){
    add(1); add(1,2); add(1,2,3);  //省略的值使用了默认参数
    return 0;
}

函数的占位参数

语法: 返回值类型 函数名 (数据类型){}

//函数占位参数
void func(int a,int){
    cout<<"this is func"<<endl;
}
int main(){
    func(10,10);  //占位参数必须填补
    return 0;
}
//占位参数也可以有默认参数
void func(int a,int = 10){
    cout<<"this is func"<<endl;
}
int main(){
    func(10);  //占位参数也不用传了
    return 0;
}

函数重载 (overload)

作用: 函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同,或者个数不同,或者顺序不同

注意: 函数的返回值不可以作为函数重载的条件

注意事项:

  1. 引用作为重载的条件

    void func(int& a){
        cout<<"func(int& a)调用"<<endl;
    }
    void func(const int& a){
        cout<<"func(const int& a)调用"<<endl;
    }
    int main(){
        int a = 10;
        func(a);  //调用无const: func(int& a)
        func(10); //调用有const: func(const int& a)
    }
  2. 函数重载碰到默认参数,应避免出现二义性的情况

函数的声明

如果函数写在main函数之后,则该函数必须在main函数前先声明


函数的分文件编写

  • 创建后缀名为.h的头文件

在头文件中包含函数需要包含的头文件

在头文件中写函数的声明

  • 创建后缀名为.cpp的源文件

在源文件中包含对应的头文件

在源文件中写函数的定义


 
 

指针 (pointer)

 

定义和初始化

A pointer denotes the memory location of a variable.

*为解引用操作符,对*p的操作就是对指针指向的内存空间的操作

对于定义的局部指针变量,其内容(地址)是随机的

必须先初始化或赋值,给予正确的地址再使用

占用空间:

32位系统上占4个字节,64位系统上占8个字节


空指针

int* p = NULL;

空指针指向的内存编号是0,而0~255之间的为系统占用内存,不允许用户访问


const 与指针

const 修饰指针

const int* p = &a;

指针的指向可以改,指针指向的 数据 不能改

const 修饰指针变量

int* const p = &a;

指针的 指向 不能改, 指针指向的数据可以改

const 既修饰指针,又修饰指针变量

const int* const p = &a;

指针的指向和指向的数据都不能改


 
 

结构体 (struct)

 

结构体的定义和使用

结构体属于用户 自定义数据类型

语法: struct 结构体名 { 结构体成员列表 };

举例:

//创建时,struct关键字不能省略
struct Student
{
    string name;
    int age;
    int score;
};

通过结构体创建变量的方式有三种:

  • struct Student s1; //创建时,struct可省略

s1.name = "张三"; //使用操作符"."访问成员

s1.age = 18;

s1.score = 100;

  • struct Student s2 = {"李四",19,80};
  • 在定义结构体时顺便创建结构体变量

    struct Student
    {
        string name;
        int age;
        int score;
    }s3;

结构体数组

创建结构体数组:

struct Student stuArr[3] = 
{
    {"张三",18,100},
    {"李四",19,80},
    {"王五",20,90}
};

给结构体中的元素赋值:

stuArr[2].age = 25;


结构体指针

作用:通过指针访问结构体中的成员

  • 利用操作符->可以通过结构体指针访问结构体属性

student* p = &s;

通过指针访问结构体变量中的数据

p->name, p->age, p->score


结构体嵌套

struct teacher
{
    int id;
    string name;
    int age;
    struct student stu;
};
teacher t;
t.stu.age = 20;

 
 

内存分区

 

代码区

存放CPU执行的机器指令

代码区是 共享的 ,频繁被执行的程序,在内存中有一份代码即可

代码区是 只读的 ,防止程序意外修改了它的指令


数据区

堆区

由程序员分配释放,若程序员不释放,程序结束后由操作系统回收

int* func()
{
    int* p = new int(10);
    return p;   //不同于前面的错误示例,这种方法是可行的
}

栈区

由编译器自动分配和释放,存放函数的参数值,局部变量等

注意事项:不要返回局部变量的地址

错误示例:

int* func(int b) //形参数据也放在栈区
{
    int a = 10;  //局部变量,存放在栈区,函数执行完后自动释放
    return &a;
}

全局区

  1. 全局变量、
  2. 静态变量static int s_a = 10;
  3. 常量: 字符串常量、const修饰的全局变量(即全局常量)

该区域的数据在 程序结束后由操作系统释放