C/C++ 基础知识 二
一、函数的概念与作用
1. 函数的定义及其语法
1.1 函数的命名与声明
函数是C/C++程序的基本构成单元,其中函数名是必要的。
以下是命名函数的要求:
- 必须以字母或下划线开头,后面可以是数字、字母或下划线
- 不能使用关键字或保留字作为函数名
- 函数名要简洁明了,尽量避免使用缩写
函数声明包括函数的返回类型、函数名、参数列表和函数体。
以下是函数声明的要求:
- 必须保证函数声明与函数定义的返回类型、函数名和参数列表相同
- 函数声明通常放在头文件中,以便在其他源文件中访问该函数
// 函数声明示例
int Add(int a, int b);
1.2 函数的参数类型与数量
函数可以有零到多个参数,每个参数都需要一个特定的类型。
以下是参数的说明:
- 参数是可选的,可以没有参数
- 参数类型与数量必须与声明的函数参数相同
- 参数可以使用默认值
// 使用默认值
int Add(int a, int b = 0);
1.3 函数的返回值类型与返回值
函数可以返回一个值或不返回值。
以下是返回值的说明:
- 函数可以返回一个值或没有返回值
- 函数的返回值类型必须与函数声明或定义中的返回类型相同
// 函数定义
int Add(int a, int b) {
return a + b;
}
2. 函数的调用方式
在C/C++中,我们通常使用以下两种方式来调用函数:值传递和引用传递。
2.1 值传递和引用传递
- 值传递:将参数的值拷贝一份传递给函数。函数内部对参数值的修改不会影响到函数外部的原始值
- 引用传递:将参数的地址传递给函数,函数内部对参数值的修改会直接影响到函数外部的原始值
#include <iostream>
using namespace std;
// 值传递
int add(int a, int b) {
a++;
return a + b;
}
// 引用传递
int sub(int &c, int &d) {
c--;
return c - d;
}
int main() {
int x = 3, y = 2;
int result1 = add(x, y);
int result2 = sub(x, y);
cout << "result1 = " << result1 << endl; // 输出:result1 = 6
cout << "result2 = " << result2 << endl; // 输出:result2 = 0
cout << "x = " << x << endl; // 输出: x = 2
return 0;
}
2.2 函数指针与回调函数
在C/C++中可以使用函数指针来调用函数。函数指针是指向函数的指针变量可以像普通变量一样传递、赋值及修改其值。函数指针可以用来实现回调函数将某一个函数的地址传递给另一个函数,在另一个函数中调用该地址从而实现回调。
#include <iostream>
using namespace std;
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
void math(int (*p)(int, int), int a, int b) {
int result = p(a, b);
cout << "result = " << result << endl;
}
int main() {
math(add, 3, 2); // 输出:result = 5
math(sub, 3, 2); // 输出:result = 1
return 0;
}
3.函数的作用和优势
在c/c++编程中函数是一种非常重要的代码结构,它可以让我们更好的组织代码,提升代码的可读性和可维护性。
下面是 c/c++ 函数的三个优势:
3.1模块化编程
模块化编程是指将复杂的系统划分成多个独立模块,每个模块只负责完成一部分功能,从而降低了整个系统的复杂度。
在c/c++中函数就是模块化编程的最小单位,我们可以将一个复杂的系统划分成多个函数,并将这些函数组合起来完成系统的功能。这样不仅可以降低系统的复杂度,还可以提高代码的重用性和可维护性。
下面是一个简单的 c++ 函数示例:
/**
* 计算两个数的和
*/
int add(int a, int b) {
return a + b;
}
int main() {
int a = 1, b = 2;
std::cout << "a + b = " << add(a, b) << std::endl;
return 0;
}
运行结果:
a + b = 3
在代码中我们定义了一个 add
函数,用来计算两个数的和。
在 main
函数中通过调用 add
函数来计算 a
和 b
的和。这样的模块化编程方式可以清晰地看到每个模块(函数)的作用,从而更容易地理解代码。
3.2代码复用性
函数的复用性是通过将代码封装在函数内部可以在不同的代码块中重复使用同一个函数,从而减少代码冗余。这样可以大大提高代码的可读性和可维护性,同时也可以减少代码的开发成本。
下面是一个简单的 c++ 函数示例,展示如何通过代码复用提高代码的可读性:
/**
* 将字符串转换成数字
*/
int str2num(std::string str) {
int num = 0;
int len = str.length();
for (int i = 0; i < len; ++i) {
num = num * 10 + (str[i] - '0');
}
return num;
}
int main() {
std::string str1 = "123";
std::string str2 = "456";
std::cout << str2num(str1) + str2num(str2) << std::endl;
return 0;
}
运行结果:
579
上面的代码中定义了一个 str2num
函数,用来将字符串转换为数字。
在 main
函数中通过调用 str2num
函数将两个字符串分别转换成数字后相加,从而计算出它们的和。通过这样的方式只需要编写一次 str2num
函数,就可以在不同的代码块中重复使用它来进行字符串和数字之间的转换。
3.3分离关注点
函数的分离关注点是通过将代码分解成多个函数,可以将不同的代码块聚焦在不同的函数中,从而让代码更加清晰易懂,减少错误发生的可能性。
下面是一个简单的 c++ 函数示例,展示如何通过分离关注点来提高代码的可维护性:
/**
* 计算平均值
*/
double average(int vals[], int len) {
double sum = 0;
for (int i = 0; i < len; ++i) {
sum += vals[i];
}
return sum / len;
}
/**
* 将数组打印到控制台
*/
void print(int vals[], int len) {
for (int i = 0; i < len; ++i) {
std::cout << vals[i] << " ";
}
std::cout << std::endl;
}
int main() {
int vals[] = {1, 2, 3, 4, 5};
int len = sizeof(vals) / sizeof(vals[0]);
print(vals, len);
std::cout << "average = " << average(vals, len) << std::endl;
return 0;
}
运行结果:
1 2 3 4 5
average = 3
上面的代码中定义了两个函数: average
和 print
,分别用来计算数组的平均值和将数组打印到控制台。
通过将不同的功能聚焦在不同的函数中可以更加清晰地看到每个函数的作用,从而降低代码的复杂度并减少错误的发生。
二、数组的概念和使用
1.数组的定义与声明
在C/C++ 中数组是一种常用的数据结构,用于存储一系列相同类型的数据
1.1一维数组
一维数组是一种只有一个维度的数组,其定义与声明方法如下:
// 定义一个长度为 n 的 int 类型数组
int arr[n];
// 定义一个长度为 10 的 double 类型数组,其初始值皆为 0.0
double arr[10] = {0.0};
其中[]
中指定了数组的长度可以是一个常量、一个宏定义、一个变量或者一个常量表达式。
可以通过 sizeof
运算符来获取数组的长度:
// 定义一个长度为 5 的 char 类型数组
char arr[] = {'h', 'e', 'l', 'l', 'o'};
// 输出 arr 数组的长度
std::cout << sizeof(arr) / sizeof(arr[0]) << std::endl;
输出:
5
1.2二维数组
二维数组是一种有两个维度的数组,其定义与声明方法如下:
// 定义一个 3 行 4 列的 int 类型数组
int arr[3][4];
// 定义一个 2 行 3 列的 char 类型数组,其初始值皆为空格字符
char arr[2][3] = {{' ', ' ', ' '}, {' ', ' ', ' '}};
1.3多维数组
在C/C++还可以使用多维数组来存储更加复杂的数据结构
定义与声明方法类似于二维数组:
// 定义一个 2 行3 列 4 深度的 int 类型数组
int arr[2][3][4];
// 定义一个 3 行 4 列 2 深度的 double 类型数组,其初始值皆为 0.0
double arr[3][4][2] = {0.0};
这样就可以通过多维数组来存储需要的数据了
2. 数组的初始化和访问
2.1 静态初始化和动态初始化
静态初始化是指在定义数组时就直接初始化数组元素的值,例如:
#include <iostream>
int main()
{
int staticArr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++)
{
std::cout << staticArr[i] << " "; // 打印数组元素
}
return 0;
}
上面的代码定义了一个名为staticArr
的整型数组,它初始化了5个元素分别为1, 2, 3, 4, 5
在程序运行时打印数组中的元素可以得到如下输出:
1 2 3 4 5
动态初始化则是在定义数组之后对数组元素逐个进行初始化,例如:
#include <iostream>
int main()
{
int arr[5];
for (int i = 0; i < 5; i++)
{
arr[i] = i + 1;
}
for (int i = 0; i < 5; i++)
{
std::cout << arr[i] << " ";
}
return 0;
}
上面的代码定义了一个名为arr
的整型数组,但是它并没有初始化,程序通过一个for循环对数组元素进行逐个初始化最终得到如下输出:
1 2 3 4 5
2.2 数组元素的访问
无论是静态初始化还是动态初始化,我们都可以通过索引值来访问数组中的元素。
数组的索引是从0开始的,例如:
#include <iostream>
int main()
{
int arr[5] = {1, 2, 3, 4, 5};
std::cout << arr[0] << std::endl; // 输出1
std::cout << arr[2] << std::endl; // 输出3
arr[3] = 10; // 修改数组元素
std::cout << arr[3] << std::endl; // 输出10
return 0;
}
3. 数组相关的操作与算法
3.1 数组操作(拷贝 排序 查找)
3.1.1 数组的拷贝
代码实现将一个数组拷贝到另一个数组中:
int main()
{
// 原数组
int arr1[5];
// 生成随机数
srand(time(NULL));
for (int i = 0; i < 5; i++) {
arr1[i] = rand() % 20;
cout << arr1[i] << " ";
}
cout << endl;
// 目标数组
int arr2[5];
// 拷贝原数组到目标数组
for (int i = 0; i < 5; i++) {
arr2[i] = arr1[i];
cout << arr2[i] << " ";
}
cout << endl;
return 0;
}
3.1.2 数组的排序
使用sort()
函数对数组进行排序。
调用sort(arr, arr+n)
函数可以对数组arr
进行从小到大的升序排序
对数组进行降序排序可以使用greater<int>()
函数对象
示例代码如下:
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;
int main()
{
// 原数组
int arr[5];
// 生成随机数
srand(time(NULL));
for (int i = 0; i < 5; i++) {
arr[i] = rand() % 20;
cout << arr[i] << " ";
}
cout << endl;
// 排序
sort(arr, arr+5, greater<int>());
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
3.1.3数组的查找
使用binary_search()
函数对有序数组进行二分查找。
调用binary_search(arr, arr+n, x)
函数,其中arr
是有序数组,arr+n
表示截止到第n
个元素,x
为要查找的元素。如果数组中存在元素x
则返回true
,否则返回false
。
示例代码如下:
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
using namespace std;
int main()
{
// 原数组
int arr[5];
// 生成随机数
srand(time(NULL));
for (int i = 0; i < 5; i++) {
arr[i] = rand() % 20;
cout << arr[i] << " ";
}
cout << endl;
// 排序
sort(arr, arr+5);
// 查找
int x = 9;
bool result = binary_search(arr, arr+5, x);
if (result) {
cout << "数组中存在元素 " << x << endl;
} else {
cout << "数组中不存在元素 " << x << endl;
}
return 0;
}
输出结果为:
8 10 14 14 17
数组中存在元素 10
3.2 数组和字符串
字符串的本质是一个字符数组。因此可以通过访问字符串中的每个字符实现对字符串的操作。
下面是一个示例代码,实现了字符串的反转:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char str[100];
cout << "请输入一个字符串:";
cin >> str;
int len = strlen(str); // 获取字符串长度
// 反转字符串
for (int i = 0; i < len/2; i++) {
char tmp = str[i];
str[i] = str[len-1-i];
str[len-1-i] = tmp;
}
cout << "反转后的字符串为:" << str << endl;
return 0;
}
模拟运行结果如下:
请输入一个字符串:HelloWorld
反转后的字符串为:dlroWolleH
3.3 数组在图像处理中的应用
在图像处理中常常需要对图片进行大量的计算和操作,而数组的高效性和灵活性使得它成为图像处理的重要工具之一
3.3.1 数组用于存储图像数据
图像可以看作是一个二维或者三维的数组,其中每个元素表示图像中的一个像素。在图像处理中通常会将图像转化为一个由整数或浮点数构成的数组,其中每个元素都代表着相应位置的像素值。对数组进行操作可以对图像进行各种处理,例如滤波、裁剪、缩放等。
创建一个用于存储灰度图像的二维数组并将其转化为图像,示例:
#include <stdio.h>
#include <stdlib.h>
#define ROWS 256
#define COLS 256
// 创建二维数组
unsigned char **allocate_2d_array(int rows, int cols) {
unsigned char **array = malloc(rows * sizeof(unsigned char *));
for (int i = 0; i < rows; i++) {
array[i] = (unsigned char *) malloc(cols * sizeof(unsigned char));
}
return array;
}
// 释放二维数组
void free_2d_array(unsigned char **array, int rows) {
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
}
// 将数组转化为图像
void array_to_image(unsigned char **array, int rows, int cols, char *filename) {
FILE *fp = fopen(filename, "wb");
fprintf(fp, "P5\n%d %d\n255\n", cols, rows);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
fputc(array[r][c], fp);
}
}
fclose(fp);
}
int main() {
// 创建一个大小为 256x256 的灰度图像
unsigned char **image = allocate_2d_array(ROWS, COLS);
for (int r = 0; r < ROWS; r++) {
for (int c = 0; c < COLS; c++) {
image[r][c] = (unsigned char) (255.0 * r / ROWS);
}
}
// 将数组转化为图像并保存
array_to_image(image, ROWS, COLS, "gray.pgm");
// 释放内存
free_2d_array(image, ROWS);
return 0;
}
上述代码在主函数中创建了一个大小为 256x256
的灰度图像,然后将二维数组转化为图像并保存,最后释放内存空间。
定义 allocate_2d_array
函数来创建二维数组,分配指向指针数组的指针各指针分别指向一个包含一行像素值的数组。使用自定义函数 free_2d_array
来释放该二维数组的内存空间。
输出的 gray.pgm
文件是以 PGM 格式存储的灰度图像,可以使用绘图软件读取它。
3.3.2 数组用于图像处理
实现对灰度图像进行均值滤波操作
#include <stdio.h>
#include <stdlib.h>
#define ROWS 256
#define COLS 256
// 创建二维数组
unsigned char **allocate_2d_array(int rows, int cols) {
unsigned char **array = malloc(rows * sizeof(unsigned char *));
for (int i = 0; i < rows; i++) {
array[i] = (unsigned char *) malloc(cols * sizeof(unsigned char));
}
return array;
}
// 释放二维数组
void free_2d_array(unsigned char **array, int rows) {
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
}
// 均值滤波
void mean_filter(unsigned char **image, int rows, int cols, int window_size) {
unsigned char **filtered_image = allocate_2d_array(rows, cols);
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
// 对当前像素进行滤波操作
int sum = 0;
for (int i = -window_size / 2; i <= window_size / 2; i++) {
for (int j = -window_size / 2; j <= window_size / 2; j++) {
int row = r + i;
int col = c + j;
if (row >= 0 && row < rows && col >= 0 && col < cols) {
sum += image[row][col];
}
}
}
filtered_image[r][c] = sum / (window_size * window_size);
}
}
// 将滤波后的图像存储起来
array_to_image(filtered_image, rows, cols, "mean_filtered.pgm");
// 释放内存
free_2d_array(filtered_image, rows);
}
int main() {
// 创建一个大小为 256x256 的灰度图像
unsigned char **image = allocate_2d_array(ROWS, COLS);
for (int r = 0; r < ROWS; r++) {
for (int c = 0; c < COLS; c++) {
image[r][c] = (unsigned char) (255.0 * r / ROWS);
}
}
// 进行均值滤波
mean_filter(image, ROWS, COLS, 5);
// 释放内存
free_2d_array(image, ROWS);
return 0;
}
上述代码中在主函数中进行了均值滤波操作并将滤波后的图像保存到文件中
首先创建了一个大小为 256×256 的灰度图像。定义mean_filter
函数对图像进行均值滤波操作,该函数支持自定义滤波器大小。
三、指针的基本概念和使用
1. 指针的定义与声明
1.1 指针的基本概念
指针是用来存储变量地址的变量,一个指针变量只能储存一个变量的地址通过指针可以访问该变量。
1.2 声明指针的形式
//格式
datatype *pointer_name;
// 示例
int *p;
float *q;
int *p
表示声明了一个指向整型变量的指针 p
float *q
表示声明了一个指向浮点型变量的指针 q
1.3 指针和数组的联系
指针和数组有着紧密联系,可以使用指针访问数组元素
使用指针访问数组的元素:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 声明一个指向整型变量的指针,指向数组 arr 的第一个元素
int *p = arr;
printf("使用指针访问数组元素:\n");
for(int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, *(p + i));
}
return 0;
}
2. 指针的使用
2.1 指针的赋值
定义一个整数a将它的值赋为10
定义一个指向整数的指针p将它的值赋为a的内存地址
使用指针可以间接地访问a的值通过*p即可访问a的值,也可以通过指针改变a的值
在使用指针的时候一定要保证指针已经指向了一个内存地址, 例如定义的q指针它没有指向任何内存地址所以不能直接操作*q
int a = 10; // 定义一个整数a,并赋值为10
int *p = &a; // 定义一个指向整数的指针p,并将p的值赋为a的内存地址
printf("a的值为:%d\n", a); // 输出10
printf("p的值为:%p\n", p); // 输出a的内存地址
printf("*p的值为:%d\n", *p); // 输出10,*p表示p所指向内存地址的值
*p = 20; // 通过指针改变a的值
printf("a的值为:%d\n", a); // 输出20
printf("*p的值为:%d\n", *p); // 输出20
int *q; // 定义一个指向整数的指针q
*q = 30; // 错误的方法,q还没有指向一个内存地址,不能直接操作*q
2.2 指针和函数
指针可以和函数配合使用用于在函数之间传递参数或返回值
在主函数中定义两个整数a和b将它们的值分别赋为10和20,通过指针调用swap函数交换a和b的值:
void swap(int *x, int *y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
int a = 10;
int b = 20;
swap(&a, &b); // 通过指针交换a和b的值
printf("a的值为:%d,b的值为:%d\n", a, b); // 输出a的值为20,b的值为10
指针作为函数的返回值示例:
在主函数中通过指针p调用create_array函数,将指向这个动态分配的数组。使用指针访问数组元素最后释放内存
定义create_array函数用于动态分配一个大小为size的整型数组,并将数组元素分别赋为0, 1, 2,…, size-1。需要注意动态分配的内存必须及时释放否则会导致内存泄漏
int *create_array(int size) {
int *arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = i;
}
return arr;
}
int *p = create_array(10); // 动态分配一个大小为10的数组
for (int i = 0; i < 10; i++) {
printf("%d ", *(p + i));
}
printf("\n");
delete[] p; // 释放内存
2.3 动态内存分配与释放
指针用于动态内存分配和释放 示例:
new
:用于动态分配内存。delete
:用于释放内存。new[]
:用于动态分配数组。delete[]
:用于释放数组内存。
用new关键字动态分配了一个整数的内存并将其值赋为10,用delete释放了这个内存
用new[]动态分配一个大小为10的整型数,将数组元素分别赋为0, 1, 2,…, size-1。最后用delete[]释放数组的内存
int *p = new int; // 动态分配一个整数的内存
*p = 10;
printf("*p的值为:%d\n", *p); // 输出10
delete p; // 释放内存
int *q = new int[10]; // 动态分配一个大小为10的整型数组
for (int i = 0; i < 10; i++) {
q[i] = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", q[i]); // 输出0 1 2 ... 9
}
printf("\n");
delete[] q; // 释放内存
3. 指针的应用和注意事项
3.1 指针和结构体
指针可以和结构体配合使用用于实现复合的数据结构,示例:
定义了一个用户结构体包含id name age三个成员变量,动态分配了一个学生结构体的内存用指针*p访问结构体的成员变量。需要注意使用指针访问成员变量时需要用->运算符
// 定义一个用户信息结构体
struct User {
int id;
char name[20];
int age;
};
User *p = new User; // 动态分配一个学生结构体的内存
p->id = 1001;
strcpy(p->name, "Tom");
p->age = 20;
printf("id=%d, name=%s, age=%d\n", p->id, p->name, p->age); // 输出id=1001, name=Tom, age=20
delete p; // 释放内存
3.2 指针和文件操作
指针可以和文件操作配合使用用于实现文件的读写操作,示例:
用ofstream获取一个输出文件流并将其写入字符串”Hello World!”最后关闭输出文件流
用ifstream获取一个输入文件流并逐个字符输出文件内容最后关闭输入文件流
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream fout("data.txt"); // 创建一个输出文件流
if (!fout) {
cout << "文件打开失败" << endl;
return 1;
}
fout << "Hello World!" << endl;
fout.close(); // 关闭输出文件流
ifstream fin("data.txt"); // 创建一个输入文件流
if (!fin) {
cout << "文件打开失败" << endl;
return 1;
}
char ch;
while (fin.get(ch)) {
cout << ch; // 逐个字符输出文件内容
}
fin.close(); // 关闭输入文件流
return 0;
}
3.3 指针的安全使用
指针是一种非常有用的工具但也需要小心使用,否则容易出现程序崩溃内存泄漏等问题
以下是一些常见的指针使用错误:
- 未初始化指针访问:当一个指针未指向任何内存地址时,如果直接访问该指针指向的内存单元会导致未知的结果。
- 超出指针范围访问:当一个指针指向的内存单元已经被释放,如果尝试访问该内存单元可能会引发程序崩溃或数据损坏。
- 内存泄漏:当一个指针动态分配的内存未被妥善释放时会导致内存泄漏,这会使程序消耗更多的内存,并可能降低程序的性能。
- 指针误用:例如将一个指针用于不同类型的变量,或者使用不正确的指针运算符,都可能产生不可预期的结果。
为了避免这些问题应该尽可能地合理使用指针,应该遵循良好的编程习惯,如:确保每个指针都指向一个已分配的内存单元,及时释放动态分配的内存,避免出现指针错误等。
四、小结回顾
本文主要介绍了C/C++编程中的函数、数组和指针的概念和使用方法,下面再来简单回顾一下:
函数它将代码模块化方便复用并返回值实现更加灵活的编程。
数组用来存储同一类型数据的一组变量可以被视为一种特殊的数据结构。
指针用于存储变量的内存地址可以与数组和函数结合使用实现更加复杂的编程需求。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/144178.html