2024.1.14
目录
2. 函数指针是指针的第三个作用,就是一个指针变量,用来指向某个函数,就是保存了这个函数的地址;
1. 当数组做函数参数时,在C语言或C++中为何必须传递数组的长度?
上午:
1.昨天作业评讲:
1. 重载Student结构中的输入和输出运算符;
在对某个类或结构进行运算符重载时,要根据实际情况选择要重载的运算符;
struct Student {
int id;
char name[41];
int age;
int scores[3];
friend ostream &
operator<<(ostream &out, const Student &obj) {
/*return out << "Student{" << obj.id << ",\"" << obj
.name << "\"," << obj.age << ",{" << obj.scores[0]
<< "," << obj.scores[1] << ","
<< obj.scores[2] << "}}";*/
out << "Student{" << obj.id;
out << ",\"" << obj.name << "\",";
out << obj.age << ",{";
out << obj.scores[0] << "," << obj.scores[1] << ","
<< obj.scores[2] << "}}";
return out;
}
friend istream &operator>>(istream &in, Student &obj) {
cout << "input id: ";
in >> obj.id;
in.get();
cout << "input name: ";
in.getline(obj.name, sizeof(obj.name));
cout << "input age: ";
cin >> obj.age;
cout << "input three scores: ";
in >> obj.scores[0] >> obj.scores[1] >> obj.scores[2];
return in;
}
};
2. 重载Value结构的+、*、>、<这四个运算符;
1. 针对以上的四个运算符,都是两个对象的运算;v1+v2, v1*v2,v1>v2,v1<v2;
2. 重载时,尽量使用友元函数的重载;所谓的友元是站在朋友的立场上,对v1和v2进行操作;
3. 还可使用成员函数进行重载时,没有友元函数重载更加直观和清晰;
4. 在类或结构中,针对特定运算符重载后,在操作对象间的运算时,就像操作内置类型变量的运算一样,比较直观;12+12 v1+v2 v1.add(v2);
5. 在运算符重载这一知识点,在所有的编程语言中,只有C++有这个功能;
struct Value {
int value;
friend ostream &operator<<(ostream &out, const Value &obj) {
out << "Value{" << obj.value << "}";
return out;
}
friend istream &operator>>(istream &in, Value &obj) {
in >> obj.value;
return in;
}
friend Value operator+(const Value &v1, const Value &v2) {
return Value{v1.value + v2.value};
}
friend Value operator*(const Value &v1, const Value &v2) {
return Value{v1.value * v2.value};
}
friend bool operator>(const Value &v1, const Value &v2) {
return v1.value > v2.value;
}
friend bool operator<(const Value &v1, const Value &v2) {
//return v1.value < v2.value;
return !(v1 > v2);
}
};
void test() {
Value v1{14};
Value v2{24};
Value vsum = v1 + v2;
Value vtimes = v1 * v2;
bool greater = v1 > v2;
cout << vsum << endl;
cout << vtimes << endl;
cout << greater << endl;
bool less = v1 < v2;
cout << less << endl;
bool equals_greater = v1 != v2;
}
* 当在Value中重载了大于运算符时,编译器就知道了如何比较
* 前者大于后者;但并不知道前者如何小于后者;
* 即知道如何比较 10 > 5,但不知道如何比较 5 < 10;
2. 指针指向数组
1. 指针指向静态数组;
在栈区中分配内存的数组,这种数组是由编译器自动分配内存,自动回收,整个过程不需要程序员干预,只使用即可;这种数组存活均在栈区中;
2. 指针指向动态数组;
在堆区存活的数组;需要向堆区中申请内存来存放数组中的所有元素,使用前需要检测内存是否申请成功,使用完后,还需要释放内存,返还堆区;
3. 在线测试指针指向数组的功能
https://pythontutor.com/visualize.html#mode=edit
示例:在下面所示区域输入代码,完成后点击“Visualize Execution”(即将所输入代码可视化执行)
点击过后出现下面右边区域,持续点击Next可观察到整个代码流程
show memory addresses(显示内存地址)
byte-level view of data(数据的字节级视图)
⚠️注意:申请时对应删除
4.示例代码:
指向一个动态数组:
#include <iostream>
using namespace std;
int main {
int number = 100;
cout<< "number = "<<number<<endl;
cout<<"&number = "<< &number <<endl;
cout<<endl;
int nn=5;
int *ptr = new int[nn];
for(int i=0;¡<nn;i++){
ptr[i]=i*i;
}
for(int i=0;i<nn;i++){
cout«<i<<"=>"«<ptr[i]<<endl;
}
delete []ptr;
ptr = nullptr;
return 0;
}
指向一个静态数组:
#include <iostream>
2 using namespace std;
3 int main() {
4
5 int array[]{11,2,3,4,100,-7,};
6 int nn=sizeof(array)/sizeof(*array);
7 for(int i=0;i<nn;i++){
8 cout<<i<<"->"<<array[i]<<endl;
9 }
10 }
在以上代码中,涵盖了C++中多种可能出现变量方式;
(1)、x; 全局变量;在整个文档中有效;
(2)、array|nn; 局部变量(函数中定义的变量称为局部变量),在函数中,从定义时起到整个函数结束;
(3)、i;块变量,只在定义它的块中有效;
#include <iostream>
using namespace std;
int x=11;
int main() {
int array[]{11,2,3,87,5,};
int nn = sizeof(array)/sizeof(*array);
for(int i=0;i<nn;i++){
cout<<i<<"->"<<array[i]<<endl;
}
int *ptr = array;
for(int i=0;i<nn;i++){
cout <<"-> "<<*ptr++<<endl;
//ptr++;
}
ptr=nullptr;
return 0;
}
在C语言和C++中,存活于栈区中的数组,数组名就是一个不带 * 号的常量指针名;它保存了数组元素0的地址;
表示地址时:&array[0] == array ;
访问时:array[0] == *array
注意:在说到数组元素时,不要说:第3个元素;而要说:元素2;对于数组的首元素,要说:元素0,不要说:第一个元素;(即注意⚠️地址下标从0开始)
为什么说数组名是一个常量指针:因为一个数组一旦确定,数组名与它所表示的数组永远绑定,其内存区域永不可改!
int *ptr = array;
上述代码表示:一个变量指针ptr,指向了常量指针array,对于数组的元素操作,除了array外,还可用ptr操作;ptr可以依次指向数组中的不同元素。
3. 测试存活于堆区的带指针分量的结构或类
强调:这样的结构或类对象,只须活动于内存中,不能写到文件里;无论C语言还是C++,基本操作中不能将指针指向的数组写入到文件中并取出来;
1、 申请内存,用来存放结构对象;
2、 分别申请内存,用来存放分量内容;
3、 每次申请内存,都要进行检测;
4、 使用堆区内存;
5、 使用堆区内存完毕,要释放内存,释放时要注意释放顺序;
6、 顺序原则:先申请的后释放;
7、 具体来说,先释放分量内存,没有顺序;
#include <iostream>
#include <stack>
#include <cstring>
#include <iomanip>
using namespace std;
void test();
int main() {
printf("\n");
test();
printf("\n");
return 0;
}
struct Person {
char *pid; //身份证号
int age; // 年龄
char *address;// 地址
};
void test() {
Person *pst = nullptr;
//向堆区申请一块内存,用来存放一个Person对象;
pst = new Person;
if (!pst) {
cout << "内存申请失败";
return;
}
pst->age = 23;
pst->pid = new char[9];
//检测略;
strcpy(pst->pid, "abcd1234");
pst->address = new char[25];
//检测略;
strcpy(pst->address, "中国 山东 青岛");
printf("\n");
cout << "id: " << pst->pid << endl;
cout << "age: " << pst->age << endl;
cout << "addr: " << pst->address << endl;
delete []pst->pid;
delete []pst->address;
delete pst;
pst = nullptr;
}
⚠️堆区中的内存必须释放
最后的安全结果:
在C语言或C++中,对于内存的使用遵循一个大的原则:
尽量少使用栈区内存,尽量多使用堆区内存;
扩展:
设置一个简单的结构,可以只有一个分量指针,这个指针分量可以是Char*的,也可以是内置类型的;在main函数中,创建一个Test类型的指针,它要向堆区申请内存,存放3个对象;包括赋值和输出; 编码实现以下功能;
代码完成:(⚠️代码还没交由老师检查,不能保证其正确性):
#include <iostream>
using namespace std;
struct Test {
int id;
char* name;
int* scores;
};
int main() {
// 创建Test类型的指针
Test* testPtr = new Test;
// 向堆区申请内存,存放3个对象
testPtr->id = 1;
testPtr->name = new char[10];
strcpy(testPtr->name, "John");
testPtr->scores = new int[3];
testPtr->scores[0] = 90;
testPtr->scores[1] = 85;
testPtr->scores[2] = 95;
// 输出对象的信息
cout << "ID: " << testPtr->id << endl;
cout << "Name: " << testPtr->name << endl;
cout << "Scores: ";
for (int i = 0; i < 3; i++) {
cout << testPtr->scores[i] << " ";
}
cout << endl;
// 释放内存
delete[] testPtr->name;
delete[] testPtr->scores;
delete testPtr;
return 0;
}
下午:
4. 指针指向函数;
1、 通过函数隶属分类(就是函数的主人是谁)
i. 内置函数(预定义函数)
也即是头文件提供的函数;printf,scanf,getchar等;直接应用于程序中的任何角落;
ii. 自定义函数;
1. 声明;声明于调用函数(多为main函数)之前;也叫函数头或函数原型;声明不需要参数名,有参数类型即可;当然参数也可有名;
2. 定义;定义在调用函数之后;函数功能的具体实现,参数必须有名字;
3. 应用;在调用函数中调用函数;
void hello(const char *);
void print(int);
void test() {
print(11);
hello("hello,gcc!!");
}
void print(int a) {
cout << "a -> " << (a) << endl;
}
void hello(const char *str) {
cout << "str -> " << (str) << endl;
}
2. 通过函数的参数及返回值类型分类
i. 无参、无返回值的函数;void print();
ii. 无参、有返回值的函数;int *get();
iii. 有参、无返回值的函数;void print(const char*);
iv. 有参、有返回值的函数;int add(int,int);
3. 当多个自定义函数进行交互(调用)时,声明尤为重要
void f0();
void f1();
void f2();
void test() {
}
void f0() {
f2();
}
void f2() {
printf("****** hello,f2() ******\n");
}
void f1() {
f2();
}
4. 根据函数是否循环调用进行分类 (37.44)
i. 普通函数;
ii. 递归函数;自己调用自己;在实现数据结构时,极为常用;创建二叉树;
5. 函数指针的概念;
1. 以运算符重载为例说明“类比”;
2. 函数指针是指针的第三个作用,就是一个指针变量,用来指向某个函数,就是保存了这个函数的地址;
从某个角度上说,函数也是一个指针类型,它和数组一样,也是个不带 * 号的常量指针;
在C语言和C++中,只有两个内置的常量指针,就是数组名和函数名;
#include <iostream>
using namespace std;
int add(int, int);
void hello();
void test() {
cout << " add -> " << (add) << endl;
cout << "hello -> " << (hello) << endl;
printf("\n");
int aa[]{11, 2, 3,};
double dd[]{3.14, 2.36};
cout << "aa -> " << (aa) << endl;
cout << "dd -> " << (dd) << endl;
}
int add(int a, int b) {
return a + b;
}
void hello() {
printf("****** hello,gcc! ******\n");
}
3. 面向对象编程中函数的分类;
i. 重载函数;C语言中没有这样的函数;C++才出现;思考:何为重载函数?
ii. 重写函数;出现在继承链中;
iii. 同类函数;在同一个范围(一个源文件,一个类或结构)内的多个函数,具有:返回值类型相同,参数列表相同,函数名不同的函数,称为“同类函数”;
int add(int,int);
int sub(int,int);
int times(int,int);
void print();
void hello();
4. 同类函数的“类型”如可设置?
将某类函数“抽象”为某种数据类型!,就是归纳总结出来函数类型;一旦有了某类函数的数据类型,就应该可以定义变量;既然定义了变量,就可以赋值;
同一范围,多个函数符合:返回值类型相同,参数列表相同,但函数名一定不同;
5. 函数类型的抽象:
用到了C语言中提供的关键字:typedef或C++提供的关键字: using;
#include <iostream>
#include <stack>
#include <cstring>
#include <iomanip>
using namespace std;
void test();
int main() {
printf("\n");
test();
printf("\n");
return 0;
}
int sub(int, int);
typedef int (*PtrFunc)(int, int); //PtrFunc即为此类函数类型名
int add(int, int);
int times(int, int);
int two(int, int);
void test() {
//定义一个指针变量,分别指向两个同类函数:add和times;
/*PtrFunc pf = nullptr;
pf = add;
auto rr = pf(10, 20);
cout << "rr -> " << (rr) << endl;
printf("\n");
pf = times;
rr = pf(10, 20);
cout << "rr -> " << (rr) << endl;
printf("\n");
pf = sub;
rr = pf(10, 20);
cout << "rr -> " << (rr) << endl;*/
PtrFunc ff[]{add, sub, times, two,};
int nn = sizeof(ff) / sizeof(*ff);
//sizef不是函数,是 个运算符
for (int i = 0; i < nn; ++i) {
auto rr = ff[i](100, 20);
cout << "rr -> " << (rr) << endl;
printf("\n");
}
}
int add(int a, int b) {
printf("****** 加法 ******\n");
return a + b;
}
int times(int a, int b) {
printf("****** 乘法 ******\n");
return a * b;
}
int sub(int a, int b) {
printf("****** 减法 ******\n");
return a - b;
}
int two(int a, int b) {
printf("****** 参数平方和 ******\n");
return a * a + b * b;
}
6.课堂练习
有一个函数:void apply(int *array,int nn,int (*func)(int));其中参数1和参数2为一个整型数组和数组的长度,参数3为对数组元素的操作。要求自行提供数据,实现这个函数的以下几个功能;
1、 将每个元素值扩大 2 倍;
2、 将每个元素值缩小 3 倍;
3、 将每个元素值平方;
作业思考:
1. 当数组做函数参数时,在C语言或C++中为何必须传递数组的长度?
当数组名作函数参数时,它会自动进行了类型退化,变成了一个普通的指针,不再代表整个数组了;如何访问操作数组中的每个元素,如何循环?这时就需要传递一个整数,表示数组长度;
#include <iostream>
#include <stack>
#include <cstring>
#include <iomanip>
using namespace std;
void test();
int main(int argc, char *argv[]) {
printf("\n");
test();
printf("\n");
return 0;
}
void apply(int *array, int nn, int (*func)(int));
int two(int elem);
int divideByThree(int elem);
int square(int elem);
void test() {
int aa[]{11, 2, 3, 47, 100, 78,};
int nn = sizeof aa / sizeof *aa;
printf("****** 源数组 ******\n");
for (int i = 0; i < nn; ++i) {
cout << setw(5) << aa[i];
}
printf("\n\n");
apply(aa, nn, two);
printf("****** 2倍之后 ******\n");
for (int i = 0; i < nn; ++i) {
cout << setw(5) << aa[i];
}
printf("\n\n");
apply(aa, nn, divideByThree);
printf("****** 2倍之后 ******\n");
for (int i = 0; i < nn; ++i) {
cout << setw(5) << aa[i];
}
printf("\n\n");
apply(aa, nn, square);
printf("****** 2倍之后 ******\n");
for (int i = 0; i < nn; ++i) {
cout << setw(5) << aa[i];
}
}
void apply(int *array, int nn, int (*func)(int)) {
if (!array || nn < 1) {
return;
}
for (int i = 0; i < nn; ++i) {
array[i] = func(array[i]);
}
}
int two(int elem) {
return 2 * elem;
}
int divideByThree(int elem) {
return elem / 3;
}
int square(int elem) {
return elem * elem;
}
2. 根据函数指针使用函数数组;
using PtrFunc = int (*)(int);
void test() {
int aa[]{11, 2, 3, 47, 100, 78,};
int nn = sizeof aa / sizeof *aa;
printf("****** 源数组 ******\n");
for (int i = 0; i < nn; ++i) {
cout << setw(5) << aa[i];
}
printf("\n");
printf("\n");
string descriptions[]{
"****** 2倍后 ******",
"****** 缩小3倍后 ******",
"****** 平方后 ******",
};
PtrFunc ff[]{two, divideByThree, square,};
int length = sizeof ff / sizeof *ff;
for (int i = 0; i < length; ++i) {
apply(aa, nn, ff[i]);
cout << descriptions[i] << endl;
for (int i = 0; i < nn; ++i) {
cout << setw(5) << aa[i];
}
printf("\n");
printf("\n");
}
}
3. 示例分析;
1. 以往我们使用函数时,只须考虑函数的声明、定义及使用三项;在常规应用中,参数参数多为内置类型及数组这样的操作;
2. 学了函数指针后,指针除了指向简单变量、数组后,还可操作函数,还可以做为另一个函数中的参数;一个函数中,可以有一到多个函数做为参数;
4. 学习C语言的几个难点;
1. 指针概念及应用;
2. 二进制文件的操作;
3. 链表;
5. 学习C++的几个难点;
1. 类的几个默认操作;
2. 运算符的重载;这个功能只有C++有;直观!
3. STL;数据结构的实现;
4. 文件操作;