简介:大一学生分享了他们的C++编程练习题目和代码,展示了C++作为面向对象编程语言的多方面应用。从基础语法到面向对象编程概念,本资源为初学者提供了一个理解C++编程基础知识和实践的平台。
1. C++基础知识介绍
C++概述
C++是一种静态类型、编译式、通用的编程语言,由Bjarne Stroustrup于1980年代在贝尔实验室开始研发,目的是在C语言的基础上增加面向对象编程、泛型编程以及异常处理等特性。C++支持过程化编程、面向对象编程和泛型编程,被认为是C语言的一个超集。C++广泛应用于软件开发领域,包括系统软件、游戏开发、桌面应用程序和嵌入式系统等。
C++的历史和发展
C++的设计目的是为了提供一种能够更自然地表达程序逻辑的编程语言,同时能够保持与C语言的兼容性。随着计算机硬件性能的不断提升,C++的复杂特性如模板编程、异常处理逐渐被更多开发者接受和使用。C++11、C++14、C++17和即将来临的C++20标准逐步引入了更多现代编程特性,增强了语言的表达力和安全性能。
C++的应用场景
由于C++的高度灵活性和性能优势,它在多个领域得到了广泛应用。例如,在游戏开发中,C++用于高性能的引擎开发,能够直接与硬件交互,优化渲染流程;在操作系统和嵌入式系统开发中,C++可以提供接近硬件的控制能力,同时加入面向对象的设计思路;此外,在高性能计算、实时系统、高频交易系统等领域,C++同样因其性能的优越性而被优先考虑。
2. 数据类型与用户定义类型
2.1 基本数据类型及其操作
2.1.1 整型、浮点型、字符型和布尔型
C++中定义了多种基本数据类型,它们是构建程序的基石。整型包括 int
, short
, long
和 long long
等,用于存储整数值。浮点型如 float
, double
, long double
用于存储小数或者非常大的数值。字符型 char
用于存储单个字符。布尔型 bool
用于表示逻辑值,其值为 true
或 false
。
以下是一些C++代码示例,演示了这些基本数据类型的声明和一些操作:
#include <iostream>
int main() {
// 声明基本数据类型的变量
int integerVar = 10;
float floatVar = 10.12f;
double doubleVar = 10.12;
long long longVar = 100000000000LL;
char charVar = 'A';
bool boolVar = true;
// 输出各个变量的值和类型大小
std::cout << "int size: " << sizeof(integerVar) << " bytes\n";
std::cout << "float size: " << sizeof(floatVar) << " bytes\n";
std::cout << "double size: " << sizeof(doubleVar) << " bytes\n";
std::cout << "long long size: " << sizeof(longVar) << " bytes\n";
std::cout << "char size: " << sizeof(charVar) << " bytes\n";
std::cout << "bool size: " << sizeof(boolVar) << " bytes\n";
return 0;
}
以上代码首先包含了 iostream
头文件,以便使用输入输出流。接着声明了各种基本数据类型的变量并对其进行了初始化,然后输出了它们的类型大小,这可以帮助我们理解在不同系统架构下,各种数据类型占用的空间大小。
2.1.2 数据类型转换和运算符
类型转换在编程中是必须掌握的一个概念。在C++中,可以通过隐式转换或显式转换来实现数据类型的转换。隐式转换通常发生在不同类型的运算中,比如赋值或函数参数传递时,由编译器自动完成。显式转换则是通过强制类型转换表达式进行的,如 (int)
、 (float)
等。
以下是一些类型转换的例子:
int main() {
int a = 10;
float b = a; // 隐式类型转换:int 转换为 float
int c = 20;
float d = static_cast<float>(c); // 显式类型转换:int 转换为 float
double e = 22.5;
float f = static_cast<float>(e); // 显式类型转换:double 转换为 float
// 使用强制类型转换来显示类型转换的必要性
int g = 25;
double h = g; // 隐式转换,将 int 转换为 double
std::cout << "h: " << h << std::endl;
// 使用 static_cast 显式转换 int 到 double
double i = static_cast<double>(g);
std::cout << "i: " << i << std::endl;
return 0;
}
2.2 用户定义类型
2.2.1 枚举类型
枚举( enum
)类型是用户定义的类型,允许为一组相关的整数常量命名。使用 enum
类型可以提高代码的可读性和维护性。
#include <iostream>
enum class Color {RED, GREEN, BLUE};
int main() {
Color myColor = Color::RED;
// 通过整数强制类型转换来输出枚举值对应的整数
std::cout << "The color " << static_cast<int>(myColor) << std::endl;
return 0;
}
在上面的例子中,我们定义了一个名为 Color
的枚举类型,它有三个可能的值: RED
, GREEN
, BLUE
。我们通过枚举类型 Color
创建了一个变量 myColor
,并将其初始化为 RED
。
2.2.2 结构体与联合体
结构体( struct
)和联合体( union
)是两种不同的复合类型,它们允许将多个数据项组合在一起。
#include <iostream>
// 定义一个结构体表示点的坐标
struct Point {
int x;
int y;
};
// 定义一个联合体表示两种不同类型的数值
union Number {
int i;
float f;
};
int main() {
Point pt = {3, 4};
Number num;
num.i = 10; // 存储一个整数
std::cout << "The integer value is: " << num.i << std::endl;
num.f = 10.5f; // 存储一个浮点数
std::cout << "The float value is: " << num.f << std::endl;
return 0;
}
在上述代码中,我们首先定义了一个 Point
结构体,用以存储一个点的 x
和 y
坐标。接着,定义了一个 Number
联合体,它可以存储一个整数或浮点数。
2.2.3 构造函数和析构函数的使用
构造函数和析构函数是类的特殊成员函数,用于创建和销毁对象。构造函数在对象实例化时调用,而析构函数在对象生命周期结束时调用。
#include <iostream>
class Example {
public:
// 构造函数
Example() {
std::cout << "Object created" << std::endl;
}
// 析构函数
~Example() {
std::cout << "Object destroyed" << std::endl;
}
};
int main() {
Example obj; // 创建对象,调用构造函数
return 0; // 对象生命周期结束,调用析构函数
}
在这个例子中, Example
类包含一个构造函数和一个析构函数。当在 main
函数中创建 Example
对象时,会输出”Object created”,而当 main
函数执行完毕后,对象被销毁,输出”Object destroyed”。
在C++编程中,理解和应用好基本数据类型及用户定义类型是构建高效、健壮程序的基础。第二章节至此介绍了从整型、浮点型等基本数据类型的操作到枚举、结构体、联合体等用户定义类型的创建,再到对象的构造与析构,为编程者展示了数据类型和用户定义类型丰富而细致的用法。这不仅为C++新手提供了坚实的学习基础,同时也为经验丰富的开发者巩固和扩展了他们的知识范围。接下来的章节将继续深入探讨C++的其他核心概念。
3. 控制结构使用
3.1 选择结构
3.1.1 if条件语句
在程序设计中,控制流程的执行取决于条件判断是一个非常重要的特性。C++ 中的 if 条件语句允许我们根据特定条件的真假来决定程序的执行路径。
最基本的 if 语句结构如下:
if (condition) {
// 如果 condition 为真,则执行这里
}
其中 condition
是一个布尔表达式,如果其结果为真(非零),则执行大括号内的代码块;如果为假(零),则跳过该代码块。
if 语句可以扩展为 if-else 结构,以便在条件不满足时提供一个备选的执行路径:
if (condition) {
// 条件为真时执行
} else {
// 条件为假时执行
}
此外,if-else 语句还可以链式使用,允许检查多个条件:
if (condition1) {
// 条件1为真时执行
} else if (condition2) {
// 条件1为假且条件2为真时执行
} else {
// 所有条件均为假时执行
}
在编写条件语句时,应当注意以下几点:
- 条件的可读性 :确保条件表达式尽可能清晰明了,这样代码的阅读者(可能包括未来的你)能够容易理解。
- 逻辑的正确性 :仔细检查逻辑判断是否符合预期,避免逻辑错误。
- 性能考虑 :复杂的逻辑表达式可能会降低代码的执行效率。如果可能,优化条件表达式,或者将其重构成更容易理解的形式。
3.1.2 switch多分支选择结构
switch
语句用于基于不同的情况执行不同的代码块。它通常比多个if-else语句更加清晰且执行效率更高。 switch
语句的基本用法如下:
switch (expression) {
case constant1:
// 当 expression 等于 constant1 时执行
break;
case constant2:
// 当 expression 等于 constant2 时执行
break;
// 可以有任意数量的 case 分支
default:
// 如果没有 case 分支匹配,则执行 default 分支
}
switch
语句中的 expression
必须是一个整型或枚举类型的结果。每个 case
后面跟着一个常量表达式,用来判断 expression
是否和其相等。如果没有匹配的 case
,则执行 default
分支。
break
语句用于终止 switch
语句的执行。如果不使用 break
,则会发生“穿透”(fall-through),即执行完一个 case
后,紧接着的 case
也会执行,直到遇到 break
或 switch
语句的结束。
下面是 switch
语句的一个使用示例:
int number = 2;
switch (number) {
case 1:
std::cout << "Number is 1\n";
break;
case 2:
std::cout << "Number is 2\n";
break;
default:
std::cout << "Number is not 1 or 2\n";
}
在这个例子中,由于 number
的值是2,程序将输出 “Number is 2”。
3.2 循环结构
3.2.1 for循环
for
循环是 C++ 中最常用的循环结构之一,它将循环控制代码集中在一起,使代码更易读。 for
循环的基本形式如下:
for (initialization; condition; update) {
// 循环体
}
-
initialization
是进入循环之前执行的初始化表达式,通常用于声明并初始化循环变量。 -
condition
是在每次循环迭代之前检查的布尔表达式。如果表达式为真,则执行循环体;否则,退出循环。 -
update
是在每次循环迭代之后执行的表达式,通常用于更新循环变量。
下面是一个 for
循环的示例:
for (int i = 0; i < 5; i++) {
std::cout << i << " ";
}
这段代码将输出 0 1 2 3 4
。 i
从0开始,每次循环增加1,直到 i
达到5时停止。
3.2.2 while和do-while循环
while
循环会在给定条件为真时重复执行一段代码。其基本形式如下:
while (condition) {
// 循环体
}
do-while
循环与 while
循环类似,但至少执行一次循环体,即使条件在第一次检查时就不成立。其结构如下:
do {
// 循环体
} while (condition);
下面是一个 while
循环的示例:
int i = 0;
while (i < 5) {
std::cout << i << " ";
i++;
}
这段代码同样输出 0 1 2 3 4
。 i
的值从0开始,并在每次迭代后递增,直到 i
等于5时停止。
而 do-while
循环的示例:
int i = 0;
do {
std::cout << i << " ";
i++;
} while (i < 5);
这段代码同样输出 0 1 2 3 4
,但是即使 i
初始值不是0,它至少会执行一次循环体。
3.2.3 循环控制语句
C++ 提供了特殊的语句来控制循环的执行流程,包括 break
和 continue
。
-
break
语句可以立即退出循环,即使循环条件还没有达到。 -
continue
语句用于跳过当前循环迭代的剩余部分,并立即开始下一次迭代。
下面是一个结合 break
和 continue
的例子:
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 当 i 等于 5 时退出循环
}
if (i % 2 == 0) {
continue; // 当 i 是偶数时跳过本次循环的剩余部分
}
std::cout << i << " ";
}
这段代码将输出 1 3
,然后因为 i
达到了5,触发 break
语句,循环被退出。注意,当 i
为偶数时,使用 continue
语句跳过输出。
总结来说,控制结构是程序流程控制的核心,使得程序能够根据条件执行不同的操作,或重复执行操作直到满足特定条件。掌握这些基础知识,可以让你编写出逻辑清晰、执行高效的程序代码。
4. 函数的定义与应用
4.1 函数基础
4.1.1 函数声明和定义
函数声明是告诉编译器函数的名称、返回类型、参数类型,以及参数的顺序,但不提供函数的实现细节。函数定义则提供了函数的完整实现。在C++中,函数声明通常放在头文件中,而定义则放在源文件中。
下面是一个函数声明和定义的简单例子:
// 函数声明在头文件中
int max(int a, int b); // 声明一个返回类型为int,参数为两个int的函数max
// 函数定义在源文件中
int max(int a, int b) {
return (a > b) ? a : b;
}
在C++中,函数声明是必须的,因为它们告诉编译器函数的接口,即使函数的定义在另一个文件中。如果函数在程序中被调用,但没有对应的声明,则会导致链接错误。
4.1.2 参数传递机制
在C++中,函数参数可以通过值传递或引用传递。值传递意味着函数接收的是原始参数值的一个副本,而在函数内部对参数的任何修改都不会影响到原始数据。引用传递则是将参数的引用传递给函数,这意味着函数内部的任何修改都会直接影响到原始数据。
参数传递方式有以下几种:
- 值传递(value passing)
- 值传递(const引用传递)
- 引用传递(非const引用传递)
下面展示了三种参数传递方式的示例代码:
void passByValue(int value) {
value = 100; // 修改副本,不会影响原始值
}
void passByConstReference(const int& value) {
// value = 100; // 错误: 不能修改const引用
}
void passByReference(int& value) {
value = 100; // 修改引用,会影响原始值
}
int main() {
int originalValue = 10;
passByValue(originalValue); // 值传递
// originalValue 的值仍然是 10
passByConstReference(originalValue); // const引用传递
// originalValue 的值仍然是 10
passByReference(originalValue); // 引用传递
// originalValue 的值现在是 100
return 0;
}
在设计函数时,选择合适的参数传递方式是很重要的。通常,对于较大的对象,使用引用传递可以避免不必要的数据复制,提高效率。而const引用传递可以在不提供修改权限的情况下提供对数据的访问。而值传递则适用于不需要修改参数值的小数据类型,或者当函数需要一个原始值的副本进行操作时。
4.2 函数高级特性
4.2.1 函数重载
函数重载是指在同一个作用域内可以声明几个功能类似的同名函数,但它们的参数类型、个数或顺序至少有一个不同。重载函数使得相同的操作可以通过同一个名字调用,但是根据不同的参数列表,编译器将自动选择正确的一个。
下面是一个简单的函数重载示例:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
在这个例子中,有两个 add
函数,它们接受不同类型的参数。编译器会根据实际调用时提供的参数类型来决定调用哪一个函数。
函数重载有一些限制,比如不能仅通过返回类型来区分重载的函数,也不能使用仅在某些平台上不一样的类型(如 short
和 int
在32位系统上可能是相同的,从而不能区分)。
4.2.2 默认参数
默认参数是函数声明时为函数的参数指定默认值。这样在调用函数时,如果没有提供该参数的值,则会使用默认值。
下面是一个使用默认参数的函数声明和调用的例子:
void printMessage(const std::string& message, int count = 1) {
for (int i = 0; i < count; ++i) {
std::cout << message << std::endl;
}
}
int main() {
printMessage("Hello, World!"); // 输出1次 "Hello, World!"
printMessage("Hello, World!", 3); // 输出3次 "Hello, World!"
return 0;
}
在这个例子中, printMessage
函数定义了默认参数 count = 1
,意味着如果没有提供 count
的值,函数将会输出消息一次。如果提供了 count
的值,则会按照提供的次数输出消息。
4.2.3 内联函数和函数模板
内联函数是在编译时将函数调用替换为函数体,以减少函数调用的开销。通常,函数调用会涉及到保存当前状态、跳转到函数体执行后再返回,这个过程是有开销的。内联函数可以减少或消除这种开销。
使用 inline
关键字在函数声明之前,可以建议编译器将函数视为内联的,但这不是强制性的。编译器可能会根据函数体的大小和其他因素来决定是否真正内联。
下面是一个内联函数的示例:
inline int square(int x) {
return x * x;
}
函数模板是一种通用的函数描述,它可以用来创建参数类型不同的函数。模板允许重用代码,并对不同类型的数据执行相同的操作。
下面是一个函数模板的示例:
template <typename T>
T add(const T& a, const T& b) {
return a + b;
}
在上面的例子中, add
函数模板可以接受任何类型的两个参数,并返回它们的和。编译器会根据传入的实际类型自动实例化相应的函数。
内联函数和函数模板都是C++语言中提供代码重用和提高效率的机制。内联函数通过减少函数调用的开销来提高效率,而函数模板通过参数化类型来实现代码的重用。
5. 类和对象概念
5.1 面向对象编程基础
面向对象编程(OOP)是C++的核心特性之一,它允许程序设计者将数据以及操作这些数据的方法封装在一起,形成一个类(Class)。类可以被看作创建对象的蓝图或模板,而对象是根据这个模板创建出的具体实体。
5.1.1 类的定义
在C++中,类是通过关键字 class
来定义的,类定义了对象的状态(通过成员变量表示)和行为(通过成员函数表示)。下面是一个简单的类定义示例:
class Rectangle {
private:
int width, height;
public:
void setValues(int w, int h) {
width = w;
height = h;
}
int area() {
return width * height;
}
};
在这个例子中, Rectangle
类有两个私有成员变量 width
和 height
,它们只能在类内部被访问和修改。类还包含两个公有成员函数: setValues
用于设置矩形的长和宽,而 area
用于计算矩形的面积。
5.1.2 对象的创建和使用
创建一个对象的过程称为实例化。根据类定义,可以创建多个具有不同状态但共享相同行为的对象。以下是创建和使用 Rectangle
类对象的代码示例:
Rectangle rect; // 创建Rectangle类的对象rect
rect.setValues(5, 3); // 调用setValues函数设置矩形的尺寸
std::cout << "Area: " << rect.area() << std::endl; // 调用area函数计算并输出矩形的面积
对象 rect
根据 Rectangle
类定义的蓝图创建,具有自己的状态(宽和高),并可以调用成员函数来展示其行为。
5.2 类的高级特性
5.2.1 访问控制和友元函数
访问控制是类的一个重要特性,它允许我们定义成员变量和成员函数的访问级别。在C++中,有三种访问级别: public
、 protected
和 private
。默认情况下,类成员是私有的( private
),而结构体( struct
)中的成员默认是公有的( public
)。
友元函数是一个类的非成员函数,它有权访问类的私有成员。为了使函数成为友元,你需要在类定义中使用关键字 friend
指定它。
class Circle {
private:
double radius;
friend double area(Circle& c); // 声明友元函数
public:
Circle(double r) : radius(r) {}
};
// 定义友元函数
double area(Circle& c) {
return 3.14 * c.radius * c.radius;
}
在这个例子中, area
函数是一个友元函数,它可以访问 Circle
类的私有成员 radius
。
5.2.2 构造函数和析构函数的高级用法
构造函数和析构函数是类的特殊成员函数,分别用于对象的初始化和销毁。构造函数可以带参数,甚至有多个不同参数的版本(即构造函数重载)。析构函数在对象生命周期结束时自动调用,执行清理工作。
class File {
private:
char* name;
public:
File(const char* filename) : name(nullptr) {
name = new char[strlen(filename) + 1];
strcpy(name, filename);
}
~File() {
delete[] name;
}
const char* getName() const {
return name;
}
};
在这个例子中, File
类有一个构造函数,用于为文件名分配内存,并将传入的文件名复制到这块内存中。析构函数负责释放这块内存。
5.2.3 静态成员与常量成员
静态成员是属于类的成员,而不是属于类的某个特定对象的。静态成员函数只能访问静态数据成员和其他静态成员函数。静态数据成员在所有对象之间共享,可以在没有创建类的实例的情况下进行访问。
class Counter {
private:
static int count; // 静态成员变量
public:
Counter() { ++count; } // 构造函数中增加计数
~Counter() { --count; } // 析构函数中减少计数
static int getCount() { // 静态成员函数
return count;
}
};
在这个例子中, Counter
类有一个静态成员变量 count
,用于跟踪创建了多少 Counter
类的对象。静态成员函数 getCount
可以用来获取当前的计数值。
常量成员函数保证不会修改类的任何成员变量,这是通过在函数声明后添加 const
关键字来实现的。这允许你将函数绑定到常量对象上。
通过理解并正确使用这些类的高级特性,开发者能够更加高效和安全地实现复杂的数据结构和算法,以面向对象的方式管理和维护大型代码库。
6. 面向对象编程特性
面向对象编程(OOP)是一种编程范式,它使用“对象”来设计软件。对象可以包含数据,以字段(通常称为属性或成员变量)的形式存在,以及代码,以过程的形式存在。C++支持面向对象编程的各种特性,如封装、继承和多态。在本章中,我们将深入探讨继承与多态的机制,以及其他面向对象的高级特性,例如抽象类、接口和模板类。
6.1 继承与多态
继承和多态是面向对象编程的两个核心概念。继承允许一个类(派生类)继承另一个类(基类)的属性和方法,而多态则允许同一个操作作用于不同的对象时具有不同的行为。
6.1.1 基类与派生类的关系
基类提供了派生类继承的通用接口和属性。派生类通过扩展基类的属性和行为来增加额外的特性。在C++中,使用冒号(:)来声明继承关系,并可选地指定继承类型(如public, protected, private)。
class Base {
public:
void function() { std::cout << "Base function called." << std::endl; }
};
class Derived : public Base {
public:
void function() override { std::cout << "Derived function called." << std::endl; }
};
以上代码展示了公共继承,其中 Derived
类继承了 Base
类,并重写了 function
方法。
6.1.2 虚函数和纯虚函数
为了实现多态性,C++利用了虚函数的概念。当基类中的函数被声明为 virtual
时,派生类可以重写该函数,允许在运行时选择正确的函数版本。纯虚函数是一个在基类中声明但没有实现(即没有函数体)的虚函数,表示这个函数必须在派生类中被实现。
class Base {
public:
virtual void pureVirtualFunction() = 0; // 纯虚函数
virtual void virtualFunction() { std::cout << "Base virtualFunction called." << std::endl; }
};
class Derived : public Base {
public:
void pureVirtualFunction() override { std::cout << "Derived pureVirtualFunction called." << std::endl; }
void virtualFunction() override { std::cout << "Derived virtualFunction called." << std::endl; }
};
6.1.3 多态的实现与应用
多态在C++中是通过虚函数表(vtable)实现的,这是一种在运行时解析函数调用的技术。利用多态,可以编写与具体对象类型无关的代码,增加程序的通用性和可扩展性。
void doSomething(Base& obj) {
obj.virtualFunction(); // 调用虚函数,运行时确定调用哪一个版本
}
Base b;
Derived d;
doSomething(b); // 输出: Base virtualFunction called.
doSomething(d); // 输出: Derived virtualFunction called.
6.2 其他面向对象特性
C++提供了多种面向对象编程的高级特性,如抽象类、接口和运算符重载。
6.2.1 抽象类和接口
抽象类是包含至少一个纯虚函数的类。它不能被实例化,但可以被继承。接口是只包含纯虚函数的抽象类,用于定义类必须实现的接口,但不提供任何实现。
class IShape {
public:
virtual void draw() = 0; // 纯虚函数,定义接口
virtual ~IShape() = default; // 虚析构函数
};
class Circle : public IShape {
public:
void draw() override {
std::cout << "Circle::draw()" << std::endl;
}
};
6.2.2 运算符重载
运算符重载是C++提供的一种机制,允许程序员定义运算符如何工作在用户定义的类型上。通过运算符重载,可以编写更自然和直观的代码。
class Complex {
private:
double real, imag;
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
};
Complex c1(1.0, 2.0), c2(3.0, 4.0);
Complex c3 = c1 + c2;
// 输出结果:c3 的实部为 4.0,虚部为 6.0
6.2.3 模板类的使用
模板类是C++泛型编程的一个重要特性,它允许创建可以与任何数据类型一起工作的类。模板类在编译时生成具体类型的实例。
template <typename T>
class Stack {
private:
std::vector<T> v;
public:
void push(T value) { v.push_back(value); }
void pop() { v.pop_back(); }
T top() { return v.back(); }
};
Stack<int> intStack;
intStack.push(10);
std::cout << "Top element is " << intStack.top() << std::endl;
在本章中,我们讨论了面向对象编程的继承、多态和其他高级特性。在第七章中,我们将进一步探讨STL的使用,它为C++开发者提供了一系列标准数据结构和算法。
简介:大一学生分享了他们的C++编程练习题目和代码,展示了C++作为面向对象编程语言的多方面应用。从基础语法到面向对象编程概念,本资源为初学者提供了一个理解C++编程基础知识和实践的平台。