c++知识整理 编程模块

最近开始看c++primer,做一点记录!

函数的基础知识:

要使用函数,必须完成:

1. 提供函数的定义

2.提供函数原型

3.调用函数

了解一下,函数是如何返回它的返回值:

函数会通过将返回值复制到指定的cpu寄存器或者内存单元中来将其返回,随后,调用程序将查看该内存单元,在这一过程中,返回函数和调用函数必须就该内存中存储的数据的类型达成一致,函数原型将返回值类型告知调用程序,而函数的定义决定被调函数应该返回什么类型的数据。在原型中提供与定义中相同的信息似乎有些多余,但是这样做确实有道理。例如要让信差从办公室的桌上取走一些物品,则向信差和办公室的同事都交代自己的意图,将提高信差完成这项工作的概率(C++ primer)

函数原型与函数定义:

函数原型: 即函数声明

函数定义: 函数的定义

1. c++中为什么需要提供函数原型

原型描述了函数到编译器的接口,也就是说,它将函数的返回值类型和参数的类型,数量告诉编译器。

例如函数原型:

double cube(double x);

首先,原型会告诉编译器cube()函数有一个double类型的参数,如果程序没有提供这样的参数,原型将让编译器能够捕获到这种错误。在cube()函数完成计算之后,将把返回值放到指定的位置(CPU寄存器或者内存中),然后调用函数(main()函数)将从这个指定的位置取得返回值,由于原型中指出了函数cube的返回值类型为double,因此编译器知道应该检索多少个字节以及如何解释它们。

这样做可以解决编译时的效率问题,如果没有函数原型,编译器在编译过程中在文件中查找函数的定义的话,会比较耗时,同时在查找的过程中会停止对main()函数的编译,而且函数很可能不在文件中,这样,编译器将无权访问函数代码。

所以,综上所述,原型可以确保以下几点:

1. 编译器正确处理函数的返回值

2. 编译器检查使用的参数数目是否正确

3. 编译器检查使用的参数类型是否正确

上述在编译阶段进行的原型化称为静态类型检查(static type checking)

函数和数组

数组做为函数的指针,将数组的首地址作为参数传递给函数,,节省可复制整个数组所需的内存空间和时间。尤其是在遇到大的数组的时候。

int sum(int array[], int size)
{
int M = sizeof array; // sizeof array返回的是指向数组元素的指针 的字节数(大小)
// 指针本身并没有指出数组的长度
}
int main(int argc, char *argv[])
{
const int arraySize = 8;
int cookies[arraySize] = {1,2,3,4,5,6,7,8};
int L = sizeof cookies; // 这里返回的是整个数组的长度(字节数)
int total = sum(cookies, arraySize);
return 0;
}

由于上面的原因,所以数组作为函数参数的时候必须显式的传递数组的大小

// 可以选择数组的起始和结束的位置
int total = sum(cookies+3, 4); // 起始位置, 数组的长度

在数组作为函数的参数时,使用const保护数组:

函数使用普通参数时,这种保护机制将自动实现,因为参数按值传递,使用的是实参的副本,但是当参数为数组时,应该考虑如何避免参数被修改。

// 使用const保护数组参数
void show_array(const double arr[], int n);

使用const限制数组参数,这意味着原始数组不一定得是常量,而是意味着不能再show_array()函数中修改数组中元素的值。

将数组的区间作为参数传入函数,在STL中,使用 “超尾” 的概念来指定区间,如数组名是指向数组第一个元素的指针,数组大小i为n,则数组名+n是执行数组中最后一个元素的后一个位置,所以会有“超尾”这个概念。

例如对之前的sum()函数做修改:

#include <iostream>
#include <algorithm>
using namespace std;
int sum(const int* begin, const int* end)
{
int total = 0;
const int* pt; // pt指向常量的指针
for(pt=begin; pt<end; pt++)
{
total += *pt;
}
return total;
}
int main(int argc, char *argv[])
{
const int arraySize = 8;
int cookies[arraySize] = {1,2,3,4,5,6,7,8};
cout << sum(cookies, cookies+arraySize) << endl;;
return 0;
}

关于const对指针的限制:
1. 可以用const来修饰指针,让指针指向一个常量对象,防止使用指针来修改指向元素的值

但是要注意:const *pt这样的声明,并不意味着pt指向的值实际就是一个常量,而实意味着对pt而言,这个值是一个常量,不能y用pt来修改他:

int age = 20; // age不是一个常量
const int* pt = &age; // pt指向的age值是一个常量。不能修改:
age += 10; // 可以修改age的值
*pt += 10; // 不能修改age的值

2.第二种使用const的方式使得无法修改指针的值:

int age = 20; // age不是一个常量
int* const pt = &age; // 指针不能被修改,只能指向age
int sage = 80;
pt = &sage; // 禁止这样修做!

3. 第三种,可以声明指向const对象的const指针:

int age = 20; // age不是一个常量
const int* const pt = &age; // 指针不能被修改,只能指向age,且不能通过指针修改age的值

函数,二维数组

// 理解二维数组作为函数参数的原理

int data[3][4] = {{1,2,3,4},{2,3,4,5},{5,6,7,8}};
int sum(int arr[][4], int size)
{
// 二维矩阵作为函数的参数
// 参数arr相当于一个指针 ,指向数组的第一个元素,
//这个数组的每一个元素都有四个元素组成,所以需要传入二维数组的列数
//因此 arr[i]是一个由四个元素组成的数组的数组名
int total = 0;
for(int i=0; i<size; i++)
{
for(j=0; j<4; j++)
{
total += arr[i][j];
}
}
return total;
}

函数与c-风格字符串

包含字符,但是不以空值字符结尾的char类型的数组只是数组,而不是字符串。意味着不必将字符串的长度传递给函数。

#include <iostream>
#include <algorithm>
using namespace std;
unsigned int c_in_str(const char* str, char target) // 寻找字符串中特定字符的个数
{
unsigned int count = 0;
while(*str) // != '\0'
{
if(*str == target)
{
count++;
}
str++;
}
return count;
}
int main(int argc, char *argv[])
{
char ghost[15] = "helloworld";
char* str = "helloworld";
cout << c_in_str(ghost, 'o') << endl;
cout << c_in_str(str, 'l') << endl;
return 0;
}

返回c-风格的字符串

#include <iostream>
#include <algorithm>
using namespace std;
char* build_str(char ch, int n)
{
char* pstr = new char[n+1]; // new动态分配内存
pstr[n] = '\0'; // 字符串结尾
while(n> 0)
{
n--;
pstr[n] = ch;
}
return pstr;
}
int main(int argc, char *argv[])
{
char* p = build_str('M', 8);
cout << p << endl;
delete []p; // 释放内存
return 0;
}

函数和结构体:

可以将一个结构赋值给另一个结构,也可以按值传递结构,就像普通变量一样。结构作为函数的参数,可以按值传递,但是按值传递的操作实际上函数中使用的是结构的副本(会将结构进行复制), 所以如果结构包含的数据量比较大的话,则复制结构需要增加内存需求,降低系统的运行速度。所以一般采用 pass by address 或者 pass by reference

#include <iostream>
#include <algorithm>
using namespace std;
// 定义结构
struct travel_time
{
int hour;
int minute;
travel_time(int hour, int minute)
{
this->hour = hour;
this->minute = minute;
}
travel_time()
{
this->hour = 0;
this->minute = 0;
}
};
const int minutes_perH = 60;
travel_time sum(travel_time, travel_time); // 计算时间之和
void show_time(travel_time); // 显示时间
int main(int argc, char *argv[])
{
// travel_time time1 = {12, 45}; // 如果没有定义构造函数,则用这种方法初始化结构体
travel_time time1(12, 45);
travel_time time2(1, 35); // 定义了构造函数的初始化方法
show_time(sum(time1, time2));
return 0;
}
travel_time sum(travel_time t1, travel_time t2)
{
travel_time total;
total.minute = (t1.minute + t2.minute) % minutes_perH;
total.hour = t1.hour + t2.hour + (t1.minute + t2.minute) / minutes_perH;
return total;
}
void show_time(travel_time t)
{
cout << "The total time is: " << t.hour << ":" << t.minute << endl;
}

坐标的转化:

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
struct rect
{
double x;
double y;
rect(double x, double y)
{
this->x = x;
this->y = y;
}
rect()
{
this->x = 0;
this->y = 0;
}
}; // 定义结构
struct polar
{
double dist;
double angle;
polar(double dist, double angle)
{
this->dist = dist;
this->angle = angle;
}
polar()
{
this->dist = 0;
this->angle = 0;
}
};
// prototype
polar rect2polar(rect r)
{
polar p;
p.dist = sqrt(r.x*r.x + r.y*r.y);
p.angle = atan2(r.y, r.x);
return p;
}
void show_polar(polar p)
{
const double rad2degree = 180.0/3.1415;
cout << "The distance is " << p.dist << endl;
cout << "The degree is " << p.angle*rad2degree << endl;
}
int main(int argc, char *argv[])
{
rect r;
polar p;
cout << "Enter the rect.x and rect.y: ";
while(cin >> r.x >> r.y)
{
p = rect2polar(r);
show_polar(p);
cout << "Next two numbers:(q to quit)";
}
return 0;
}

运行结果:

while(cin >> x >> y), cin是一个istream对象, cin >> x也是一个istream对象,因此cin >> x >> y最终返回的也是一个istream对象(cin), 而cin在被用于测试表达式中时,将根据输入是否成功,被转换为bool型的true和false.所以如果输入q, cin将知道输入不正确,从而q将被留在队列中,并返回一个被转换为false的值。

递归与分治:

例:绘制标尺

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
// 绘制标尺
void subdivide(char ar[], int low, int high, int level)
{
// 递归函数, 采用分治的方法
if(level == 0)
{
return;
}
int mid = (low + high) / 2;
ar[mid] = '|';
subdivide(ar, low, mid, level-1);
subdivide(ar, mid, high, level-1);
}
int main(int argc, char *argv[])
{
const int LEN = 66;
const int divs = 6;
char ruler[LEN];
// 初始化ruler
ruler[LEN-1] = '\0';
for(int i=1; i<LEN-2; i++)
{
ruler[i] = ' ';
}
int min_index = 0;
int max_index = LEN-2;
ruler[min_index] = ruler[max_index] = '|';
cout << ruler << endl;
for(int i=1; i<=divs; i++)
{
subdivide(ruler, min_index, max_index, i);
cout << ruler << endl;
// 将数组设置为空,以便进行下一次的分割
for(int j=1; j<LEN-2; j++)
{
ruler[j] = ' ';
}
}
return 0;
}

运行结果:

------------------------------------------------------------------分割线------------------------------------------------------------------

posted @   Alpha205  阅读(146)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示