第一章 命令编译链接文件 make文件
第二章 进入c++
第三章 处理数据
第四章 复合类型 (上)
第四章 复合类型 (下)
第五章 循环和关系表达式
第六章 分支语句和逻辑运算符
第七章 函数——C++的编程模块(上)
本章重要点注意函数指针,const指针参数。
其他的其实都简简单单
分类总结:
提供函数定义
:这是声明函数的行为和功能所在地,包括函数返回类型、函数名、参数列表及函数体。
提供函数原型
:也称为函数声明,它在编译时告诉编译器函数的名称、返回类型和参数列表。不包含函数体。
调用函数
:这是执行已定义函数的过程。通过使用函数名和参数列表,可以在程序的其他部分调用函数来执行特定任务。
库函数
:预先编译和定义好的函数。只需正确地调用这些函数即可。如标准C库中的strlen()函数。
自定义函数
:由程序员创建并处理其定义、原型和调用。
问题与答案:
问:什么是函数定义?
答:函数定义描述了函数的具体实现,包括函数的返回类型、函数名、参数列表(如果有的话)以及函数体,函数体中包含了实现函数功能的代码。
问:什么是函数原型?为何我们需要它?
答:函数原型又被称为函数声明,它通知编译器函数的名称、返回类型和参数列表。我们需要函数原型以便于在编译时能够进行适当的类型检查,并确定如何调用函数。
问:如何调用一个函数?
答:使用函数名和参数(如果有的话)来调用函数。例如,在上述程序中,我们通过simple()
来调用函数。
问:什么是库函数?如何使用它?
答:库函数是预先定义好的函数,存储在库中。我们可以直接使用这些函数,不需要知道它们的具体实现。例如,strlen()就是标准库中的一个函数,我们可以通过包含对应的头文件cstring,然后在程序中直接调用即可。
问:什么是using编译指令?
答:using编译指令在C++中通常用于引入命名空间。例如using namespace std;
表示我们将使用std命名空间中的名称。在函数中使用cout时,加上这条语句能简化代码,避免每次都要写std::cout。
分类总结:
函数原型
:描述了函数到编译器的接口,告诉编译器函数返回值的类型(如果有的话)以及参数的类型和数量。
函数原型是一条语句,必须以分号结束。在原型中可以提供变量名,但也可以只提供参数类型。
函数调用
:使用函数名和参数来执行函数。如果函数有返回值,调用可以出现在赋值语句中。
原型的功能
: 原型帮助编译器正确处理函数返回值,检查使用的参数数目是否正确,检查使用的参数类型是否正确;如果不正确,则转换为正确的类型(如果可能的话)。
静态类型检查
:在编译阶段进行的原型化被称为静态类型检查,可以捕获许多在运行阶段非常难以捕获的错误。
问题与答案:
问:什么是函数原型?
答:函数原型是一个声明,它告诉编译器函数的名称、返回类型、参数类型和参数数量。原型帮助编译器进行类型检查,确保函数调用时的参数类型和数量与原型匹配。
问:为什么需要函数原型?
答:函数原型向编译器描述了函数的接口,包括函数的返回类型和参数类型/数量等。这使得编译器可以在编译阶段就进行类型检查,避免运行时发生错误。
问:函数原型如何影响函数调用?
答:函数原型确定了函数调用的正确形式,包括传递给函数的参数数量和类型以及函数返回的值的类型。如果函数调用与原型不匹配,编译器会报错。
问:什么是静态类型检查?
答:静态类型检查是编译期间进行的类型检查过程。通过比较函数调用与函数原型,编译器可以确保类型的正确性,并在编译时捕获可能的类型错误。
问:在C++中,原型是可选的还是必须的?
答:在C++中,原型是必不可少的。这有助于静态类型检查,并能够在编译阶段捕获许多类型错误。
分类总结:
按值传递
:C++通常按值传递参数,即将数值参数传递给函数,而后者将其赋给一个新的变量。
形参和实参
:用于接收传递值的变量被称为形参。传递给函数的值被称为实参。
局部变量
:在函数中声明的变量(包括参数)是该函数私有的,被称为局部变量。这些变量在函数被调用时被创建,在函数结束时被销毁。
自动变量
:局部变量也被称为自动变量,因为它们是在程序执行过程中自动被分配和释放的。
问题与答案:
问:什么是按值传递?
答:按值传递是一种参数传递方式,即将实际参数值复制一份给函数形参,形参的修改不会影响到实际参数。
问:什么是形参和实参?
答:形参是函数定义时的参数,只有在函数被调用时才分配内存空间。实参是调用函数时传给函数的参数, 它可以是常量、变量或表达式。
问:什么是局部变量?
答:局部变量是在函数体内声明的变量,只在该函数的执行期间存在。
问:什么是自动变量?
答:自动变量是在函数内部声明的变量,它们在函数开始时创建,在函数结束时销毁。这些变量只存在于函数的生命周期内。
知识点总结:
函数和数组
可以结合使用,比如在需要对数组元素进行累加的场景中,我们可以设计一个函数完成这项工作,避免了每次计算都要重写循环代码。
函数接口
的设计应考虑其功能需求。例如,如果函数的任务是计算总数,那么它应返回结果(答案),并需要知道要对哪个数组进行累积,因此需要将数组名作为参数传递给它。同时,为使函数通用,不限于特定长度的数组,还需要传递数组长度。
在函数声明中,形参被声明为数组名,看起来像这样:
int sum_arr(int arr[], int n) // arr = array name, n = size
虽然这样看起来arr是数组,但实际上,arr并不是数组,而是一个指针
!尽管如此,你仍然可以在函数的其他部分把arr当成数组来使用。
在大多数情况下,C++将数组名解释为其第一个元素的地址
。但是,这个规则有一些例外,例如在数组声明中,或者当使用sizeof操作符或地址运算符&对数组名进行操作时。
当我们在函数调用中传递数组时,实际上传递的是该数组的地址。因此,如果数组元素的类型为int,则数组的类型必须为int指针,即int *
。
在C++中,当(且仅当)用于函数头或函数原型中,int *arr和int arr [ ]的含义是相同的
,都表示arr是一个int指针。不过,数组表示法(int arr[ ])提示用户,arr不仅指向int,还指向int数组的第一个int。
尽管变量arr实际上就是一个指针,但我们仍然可以像使用数组名一样使用arr。即,无论arr是指针还是数组名,表达式arr [i]
都表示数组的第i个元素。
对于遍历数组而言,使用指针加法和数组下标是等效的。记住下面两个恒等式:
arr[i] == *(arr + i) // values in two notations
&arr[i] == arr + i // addresses in two notations
重要问题及答案:
问题:如何将函数和数组结合使用?
答案:在函数声明中,可以将形参声明为数组,即使在实际使用中该形参将为指针。
问题:在函数接口设计中,需要考虑哪些内容?
答案:函数接口的设计应考虑其功能需求。例如,如果函数的任务是计算总数,那么它应返回结果,并需要知道要对哪个数组进行累积,因此需要将数组名作为参数传递给它。同时,为使函数通用,不限于特定长度的数组,还需要传递数组长度。
问题:在函数声明中,形参被声明为数组名,但实际上它是什么?
答案:尽管形参在函数声明中被声明为数组名,但实际上它是一个指针。
问题:如何在函数内部使用形参(被声明为数组)?
答案:在函数内部,可以像使用数组名一样使用这个形参。
问题:在C++中,数组名是如何被解释的?
答案:在大多数情况下,C++将数组名解释为其第一个元素的地址。
问题:当我们传递数组时,实际上传递的是什么?
答案:当我们传递数组时,实际上传递的是该数组的地址,因此,如果数组元素的类型为int,则数组的类型必须为int指针,即int *。
问题:在函数头或函数原型中,int *arr
和int arr [ ]
有何不同?
答案:在函数头或函数原型中,int *arr
和int arr [ ]
的含义是相同的,都表示arr是一个int指针。不过,数组表示法(int arr[ ])提示用户,arr不仅指向int,还指向int数组的第一个int。
问题:如何使用指针来访问数组元素?
答案:无论arr是指针还是数组名,表达式arr [i]
都可以用来访问数组的第i个元素。其中,arr[i]
等价于*(arr + i)
,而&arr[i]
等价于arr + i
。
int sum_arr(const int * begin, const int * end)
{
const int * pt;
int total = 0;
for (pt = begin; pt != end; pt++)
total = total + *pt;
return total;
}
知识点的总结:
数组的处理
:在C++中处理数组时,需将数据种类、数组起始位置和元素数量提交给函数。传统方法是将指向数组起始处的指针作为一个参数,数组长度作为另一个参数。元素区间
:通过传递两个指针来标识元素区间,一个指针标识数组的开头,另一个指针标识数组的尾部。超尾概念
:在定义区间时,STL采用“超尾”概念,即对于数组而言,标识数组结尾的参数是指向最后一个元素后面的指针。例如,如果有double数组elbuod[20],那么elbuod和elbuod + 20定义了区间。sum_arr()函数
:这个函数接收两个指针,一个指向数组的开始,另一个指向数组的结束。函数内部通过for循环,将指针从begin移动到end,同时将每个元素的值累加到total中。重要问题:
Q1:什么是“超尾”概念?
A1:“超尾”概念是指,对于数组而言,标识数组结尾的参数将是指向最后一个元素后面的指针。
Q2:如何通过指针来定义数组的区间?
A2:可以通过传递两个指针来定义数组的区间,其中一个指针标识数组的开头,另一个指针标识数组的尾部。
Q3:sum_arr()函数是如何工作的?
A3:sum_arr()函数接收指向数组的开始和结束的两个指针。然后,它会通过for循环,将指针从begin移动到end,同时将每个元素的值累加到total中,并返回total值。
这里我详细讲下怎么才能理解指针定义要怎么写
有个数组 int data[3][4],我该如何定义个指针指向它
这部分主要讲解了C++中函数和二维数组的处理。以下是知识点的总结:
二维数组与函数
:二维数组作为参数传递给函数时,相应的形参是一个指针。因此,需要正确地声明指针。例如,如果有一个二维数组data[3][4],那么将其作为参数传递给sum()函数时,sum()的原型可以是 int sum(int (*ar2)[4], int size) 或者 int sum(int ar2[][4], int size)。指针类型指定列数
:在上述sum()函数的原型中,指针类型(int (*ar2)[4]或int ar2[][4])指定了列数,这就是为什么不将列数作为独立的函数参数进行传递的原因。对二维数组的访问
:可以通过两次方括号进行访问,如ar2[r][c],也可以通过两次对指针执行解除引用操作来访问,即*(*(ar2 + r) + c)。重要问题:
Q1:如何将二维数组作为参数传递给函数?
A1:当二维数组作为参数传递给函数时,相应的形参是一个指针。例如,如果有一个二维数组data[3][4],其可以作为参数传递给sum()函数,sum()的原型可以是 int sum(int (*ar2)[4], int size) 或者 int sum(int ar2[][4], int size)。
Q2:为什么在函数形参中不需要指定二维数组的列数?
A2:这是因为在函数原型中,指针类型(int (*ar2)[4]或int ar2[][4])已经指定了列数,所以不需要再将列数作为独立的函数参数进行传递。
Q3:如何访问二维数组的元素?
A3:可以通过两次方括号进行访问,如ar2[r][c],也可以通过两次对指针执行解除引用操作来访问,即*(*(ar2 + r) + c)。
数组名与指针对应是好事吗?确实是一件好事。将数组地址作为参数可以节省复制整个数组所需的时间和内存。
如果数组很大,则使用拷贝的系统开销将非常大;程序不仅需要更多的计算机内存,还需要花费时间来复制大块的数据。
另一方面,使用原始数据增加了破坏数据的风险。
在经典的C语言中,这确实是一个问题,但ANSI C和C++中的const
限定符提供了解决这种问题的办法。
知识点总结:
数组是一种重要的数据结构
,它在程序设计中起着关键的作用。编写特定的函数来处理特定的数组操作可以提高程序的可靠性和便于修改与调试。
当需要对数组执行操作时,有三个基本的操作:将值读入到数组中、显示数组内容和重新评估每种元素的值。其中,数组不只是被传递给那些需要修改它的函数,也可以被传递给那些只需要读取它的函数。
在声明形参时使用关键字const
可以防止函数无意中修改数组的内容。
如果一个函数需要修改数组,其原型可能类似于:void f_modify(double ar[], int n);
。如果函数不修改数组,其原型可能类似于:void _f_no_change(const double ar[], int n);
。
函数不能使用sizeof来获悉原始数组的长度,而必须依赖于程序员传入正确的元素数。
重要问题及答案:
问题:如何通过函数将值赋给数组元素?
答案:我们可以编写一个接受数组名参数的函数,由于这个函数访问的是原始数组,而不是其副本,因此可以通过调用该函数将值赋给数组元素。
问题:为什么在声明形参时使用关键字const?
答案:在声明形参时使用关键字const可以防止函数无意中修改数组的内容,这对于需要保护原始数组不被改变的情况非常有用。
问题:函数如何获取原始数组的长度?
答案:函数不能使用sizeof来获悉原始数组的长度,而必须依赖于程序员传入正确的元素数。
问题:在什么情况下函数会修改传入的数组?
答案:只有当函数的目的就是修改传递给它的数据时,函数才会修改数组。例如,在一个需要重新评估每个数组元素值的函数中,由于该函数需要修改数组的值,因此在声明数组参数时,不能使用const。
知识点的总结:
const与指针
:可以有两种方式将const与指针一同使用。一种是让指针指向一个常量对象,防止使用该指针来修改所指向的值;另一种是将指针本身声明为常量,防止改变指针指向的位置。指向常量的指针
:例如const int * pt,pt指向的元素不能通过pt来进行修改。指针本身为常量
:例如int * const finger,finger只能指向初始的那个对象,不能改变指向,但可以改变指向对象的值。指向const对象的const指针
:例如const double * const stick,stick只能指向初始的那个对象同时不能改变对象的值。重要问题:
Q1:如何理解"指向常量的指针"和"指针本身为常量"?
A1:"指向常量的指针"是指该指针指向的元素不能被通过该指针来修改。而"指针本身为常量"则指的是指针在初始化后,就不能再更改其指向的对象,但是可以改变其所指向对象的值。
Q2:对于指针,const的作用是什么?
A2:const可以用来防止指针修改它所指向的值或者防止改变指针自身。
Q3:什么是"指向const对象的const指针"?
A3:"指向const对象的const指针"指的是指针本身不能改变其指向且无法通过指针来改变其所指向对象的值。
答:静态类型检查是编译期间进行的类型检查过程。通过比较函数调用与函数原型,编译器可以确保类型的正确性,并在编译时捕获可能的类型错误。
答:按值传递是一种参数传递方式,即将实际参数值复制一份给函数形参,形参的修改不会影响到实际参数。
还有传引用的刚好和这相反,不复制,直接传本身,修改将影响实际
答案:尽管形参在函数声明中被声明为数组名,但实际上它是一个指针。
答案:在函数头或函数原型中,int *arr和int arr [ ]的含义是相同的,都表示arr是一个int指针。不过,数组表示法(int arr[ ])提示用户,arr不仅指向int,还指向int数组的第一个int。
答案:无论arr是指针还是数组名,表达式arr [i]都可以用来访问数组的第i个元素。其中,arr[i]等价于*(arr + i),而&arr[i]等价于arr + i。
答案:
"指向常量的指针"是指该指针指向的元素不能被通过该指针来修改。
而"指针本身为常量"则指的是指针在初始化后,就不能再更改其指向的对象,但是可以改变其所指向对象的值。