C++入门与基础
C++ 基础概念
1. 程序与编程语言
在计算机领域,程序是一系列指令的集合,计算机按照这些指令顺序执行,以完成特定任务。而编程语言则是用于编写程序的工具,它提供了一种让程序员与计算机进行沟通的方式。C++ 作为一种强大的编程语言,广泛应用于系统软件、游戏开发、人工智能等众多领域。
2. C++ 语言特点
C++ 融合了高级语言的易用性和低级语言对硬件的直接访问能力。它具有以下显著特点:
- 面向对象编程(OOP):支持封装、继承和多态等特性,使得代码的组织和维护更加容易,提高了代码的可重用性。例如,一个游戏角色类可以封装其属性(如生命值、攻击力)和行为(如攻击、移动),通过继承可以创建不同类型的角色,利用多态实现不同角色的差异化行为。
- 高效性:C++ 生成的代码执行效率高,适合对性能要求苛刻的应用场景,如实时游戏、大型数据库管理系统等。这得益于它对内存的精细控制和直接访问硬件的能力。
- 灵活性:C++ 允许程序员在不同的抽象层次上编写代码,可以使用面向对象编程,也可以进行过程式编程,甚至可以直接操作内存和硬件。
开发环境搭建
1. 选择编译器
- GCC:GNU 编译器集合(GCC)是一款广泛使用的开源编译器,支持多种编程语言,包括 C++。在 Linux 系统中,通常已经预装了 GCC。在 Windows 系统上,可以通过 MinGW 或 Cygwin 来安装 GCC。例如,在 Ubuntu 系统中,可以通过以下命令安装 GCC:
sudo apt-get update
sudo apt-get install g++
- Clang:Clang 是 LLVM 编译器项目的 C、C++ 和 Objective - C 编译器前端。它以快速的编译速度和友好的错误提示著称。安装 Clang 的方式因系统而异,以 macOS 为例,可以使用 Homebrew 安装:
brew install llvm
- Visual C++ 编译器:对于 Windows 平台,微软提供了 Visual C++ 编译器,它集成在 Visual Studio 开发环境中。可以从微软官网下载并安装 Visual Studio Community Edition,在安装过程中选择安装 C++ 相关组件。
2. 选择集成开发环境(IDE)
- CLion:由 JetBrains 开发,专为 C 和 C++ 开发设计。它具有智能代码补全、强大的调试功能、代码分析和重构工具等。CLion 支持跨平台使用,在 Windows、macOS 和 Linux 上均可运行。
- Eclipse CDT:基于 Eclipse 平台的 C/C++ 开发工具,是一款免费的开源 IDE。它提供了丰富的插件生态系统,可以根据项目需求进行扩展。
- Visual Studio Code:微软开发的轻量级代码编辑器,通过安装 C/C++ 扩展插件,可以成为功能强大的 C++ 开发环境。VS Code 具有快速启动、占用资源少等优点,并且支持多种操作系统。
基本数据类型
1. 整数类型
- char:字符类型,通常占用 1 个字节。它可以表示单个字符,在内存中以 ASCII 码的形式存储。例如:
char ch = 'A';
- short:短整型,一般占用 2 个字节,用于表示较小范围的整数。
- int:整型,是最常用的整数类型,占用 4 个字节,可表示的范围比 short 更大。
- long:长整型,占用 4 或 8 个字节(取决于系统),用于表示更大范围的整数。
- long long:长长整型,至少占用 8 个字节,可表示非常大的整数。
不同整数类型有不同的取值范围,例如在 32 位系统上,int
的取值范围是 -2147483648
到 2147483647
。
2. 浮点类型
- float:单精度浮点型,占用 4 个字节,可表示大约 7 位有效数字。例如:
float f = 3.14f;
- double:双精度浮点型,占用 8 个字节,可表示大约 15 到 17 位有效数字。
- long double:长双精度浮点型,占用的字节数在不同系统上有所不同,通常比 double 能表示更大范围和更高精度的浮点数。
浮点类型用于表示带有小数部分的数字,由于计算机内部以二进制表示浮点数,可能会存在精度损失问题。例如:
float a = 0.1f;
float b = 0.2f;
if (a + b == 0.3f) {
std::cout << "相等" << std::endl;
} else {
std::cout << "不相等" << std::endl;
}
上述代码中,a + b
可能并不严格等于 0.3f
,因为 0.1
和 0.2
在二进制表示中是无限循环小数,存储时会有精度损失。
3. 布尔类型
- bool:布尔类型,占用 1 个字节,只有两个取值
true
和false
,用于表示逻辑值。例如:
bool isTrue = true;
if (isTrue) {
std::cout << "条件为真" << std::endl;
}
变量与常量
1. 变量定义与声明
变量是程序中用于存储数据的内存位置。在 C++ 中,变量必须先声明后使用。声明变量时需要指定变量的类型和名称,例如:
int num; // 声明一个整型变量 num
num = 10; // 给变量 num 赋值
也可以在声明时同时初始化变量:
int num = 10; // 声明并初始化整型变量 num
2. 变量作用域
变量的作用域是指程序中可以访问该变量的区域。C++ 中有以下几种作用域:
- 局部作用域:在函数内部声明的变量具有局部作用域,只在该函数内有效。例如:
void func() {
int localVar = 10;
// localVar 只能在 func 函数内访问
}
- 全局作用域:在所有函数外部声明的变量具有全局作用域,在整个程序中都可以访问。例如:
int globalVar;
void func1() {
globalVar = 20;
}
void func2() {
std::cout << globalVar << std::endl;
}
- 块作用域:在一对花括号
{}
内声明的变量具有块作用域,例如if
、for
、while
语句块中的变量。
3. 常量
常量是在程序运行过程中值不能被改变的量。C++ 中有两种定义常量的方式:
- 使用
const
关键字:
const int MAX_VALUE = 100;
// MAX_VALUE 的值不能被修改
- 使用
#define
预处理指令:
#define PI 3.14159
// 这里 PI 是一个宏常量,在预处理阶段会进行文本替换
const
定义的常量具有类型检查,而 #define
只是简单的文本替换,没有类型检查,因此在现代 C++ 编程中,更推荐使用 const
来定义常量。
运算符与表达式
1. 算术运算符
算术运算符用于执行基本的数学运算,包括 +
(加)、-
(减)、*
(乘)、/
(除)和 %
(取模)。例如:
int a = 10;
int b = 3;
int sum = a + b;
int quotient = a / b;
int remainder = a % b;
需要注意的是,整数除法会舍去小数部分,如果需要得到精确的结果,可以将其中一个操作数转换为浮点数,例如:
double result = static_cast<double>(a) / b;
2. 赋值运算符
赋值运算符 =
用于将右侧的值赋给左侧的变量。此外,还有复合赋值运算符,如 +=
、-=
、*=
、/=
等。例如:
int num = 5;
num += 3; // 等价于 num = num + 3;
3. 比较运算符
比较运算符用于比较两个值的大小关系,结果为 true
或 false
。常见的比较运算符有 ==
(等于)、!=
(不等于)、>
(大于)、<
(小于)、>=
(大于等于)和 <=
(小于等于)。例如:
int a = 10;
int b = 20;
bool isGreater = a > b;
4. 逻辑运算符
逻辑运算符用于组合多个逻辑表达式,结果也是 true
或 false
。主要的逻辑运算符有 &&
(逻辑与)、||
(逻辑或)和 !
(逻辑非)。例如:
bool condition1 = true;
bool condition2 = false;
bool result1 = condition1 && condition2; // 逻辑与,两个条件都为真时结果为真
bool result2 = condition1 || condition2; // 逻辑或,只要有一个条件为真结果就为真
bool result3 =!condition1; // 逻辑非,取反
5. 位运算符
位运算符用于对整数的二进制位进行操作,包括 &
(按位与)、|
(按位或)、^
(按位异或)、~
(按位取反)、<<
(左移)和 >>
(右移)。例如:
int a = 5; // 二进制表示为 00000101
int b = 3; // 二进制表示为 00000011
int andResult = a & b; // 按位与,结果为 00000001,即 1
int leftShiftResult = a << 2; // 左移 2 位,结果为 00010100,即 20
控制结构
1. if - else 语句
if - else
语句用于根据条件执行不同的代码块。基本语法如下:
if (condition) {
// 当 condition 为 true 时执行的代码块
} else {
// 当 condition 为 false 时执行的代码块
}
if - else
语句可以嵌套使用,以处理更复杂的条件判断:
int num = 10;
if (num > 0) {
if (num % 2 == 0) {
std::cout << "正数且为偶数" << std::endl;
} else {
std::cout << "正数且为奇数" << std::endl;
}
} else {
std::cout << "非正数" << std::endl;
}
2. switch - case 语句
switch - case
语句用于根据一个整型或枚举类型的表达式的值,选择执行不同的分支。语法如下:
int day = 3;
switch (day) {
case 1:
std::cout << "星期一" << std::endl;
break;
case 2:
std::cout << "星期二" << std::endl;
break;
case 3:
std::cout << "星期三" << std::endl;
break;
default:
std::cout << "未知的日期" << std::endl;
}
在 switch - case
语句中,break
语句用于跳出 switch
块,防止执行完当前 case
后继续执行下一个 case
。default
分支在表达式的值与所有 case
常量表达式的值都不匹配时执行。
3. for 循环
for
循环用于重复执行一段代码,适用于已知循环次数的情况。基本语法如下:
for (initialization; condition; increment) {
// 循环体
}
例如,计算 1 到 10 的累加和:
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
std::cout << "累加和为: " << sum << std::endl;
在 for
循环中,initialization
只在循环开始时执行一次,condition
在每次循环开始时检查,increment
在每次循环结束时执行。
4. while 循环
while
循环用于在条件为真时重复执行代码块,适用于不确定循环次数的情况。语法如下:
int num = 1;
while (num <= 5) {
std::cout << num << std::endl;
num++;
}
在 while
循环中,condition
在每次循环开始时检查,如果条件一开始就为 false
,则循环体一次都不会执行。
5. do - while 循环
do - while
循环与 while
循环类似,但它会先执行一次循环体,然后再检查条件。语法如下:
int num = 1;
do {
std::cout << num << std::endl;
num++;
} while (num <= 5);
do - while
循环确保循环体至少执行一次。
函数
1. 函数定义与声明
函数是一段完成特定任务的代码块。函数定义包括函数头和函数体,例如:
int add(int a, int b) {
return a + b;
}
这里 int
是函数的返回类型,add
是函数名,(int a, int b)
是函数的参数列表,{ return a + b; }
是函数体。
函数声明用于告诉编译器函数的名称、参数类型和返回类型,以便在调用函数之前让编译器知道函数的存在。例如:
int add(int a, int b); // 函数声明
函数声明和函数定义可以分开,通常将函数声明放在头文件(.h
)中,函数定义放在源文件(.cpp
)中。
2. 函数参数与返回值
函数参数是调用函数时传递给函数的值,函数可以有零个或多个参数。返回值是函数执行完成后返回给调用者的值,通过 return
语句返回。例如:
double divide(double a, double b) {
if (b == 0) {
std::cerr << "除数不能为零" << std::endl;
return -1; // 错误返回值
}
return a / b;
}
在调用函数时,需要根据函数定义提供正确数量和类型的参数,例如:
double result = divide(10.0, 2.0);
3. 函数重载
函数重载是指在同一作用域内,可以有多个同名函数,但它们的参数列表不同(参数个数、类型或顺序不同)。编译器会根据调用函数时提供的参数来决定调用哪个函数。例如:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
调用 add
函数时,编译器会根据参数类型来选择合适的函数:
int intResult = add(2, 3);
double doubleResult = add(2.5, 3.5);
4. 递归函数
递归函数是指在函数内部调用自身的函数。递归函数需要有一个终止条件,否则会导致无限递归。例如,计算阶乘的递归函数:
int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
}
return n * factorial(n - 1);
}
在上述代码中,当 n
为 0 或 1 时,函数返回 1,这是终止条件。否则,函数通过递归调用 factorial(n - 1)
来计算 n
的阶乘。
数组
1. 一维数组
数组是一种用于存储多个相同类型数据的集合。一维数组的定义方式如下:
int numbers[5]; // 定义一个包含 5 个整型元素的数组
也可以在定义时初始化数组:
int numbers[5] = {1, 2, 3, 4, 5};
数组元素通过下标访问,下标从 0 开始,例如:
int firstNumber = numbers[0];
2. 二维数组
二维数组可以看作是一个表格,有行和列。定义方式如下:
int matrix[3][4]; // 定义一个 3 行 4 列的二维数组
初始化二维数组:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
访问二维数组元素需要使用两个下标,例如:
int element = matrix[1][2]; // 访问第 2 行第 3 列的元素
3. 数组作为函数参数
数组可以作为函数的参数传递,但需要注意的是,数组作为参数传递时,实际上传递的是数组的首地址,而不是整个数组的副本。例如:
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
调用该函数时:
int numbers[5] = {1, 2, 3, 4, 5};
printArray(numbers, 5);
指针
1. 指针的基本概念
指针是一个变量,它存储的是另一个变量的内存地址。定义指针变量时需要使用 *
运算符,例如:
int num = 10;
int *ptr = # // 定义一个指向整型变量 num 的指针 ptr
这里 &
运算符用于获取变量的地址。通过指针可以间接访问所指向的变量的值,例如:
std::cout << *ptr << std::endl; // 输出 10
2. 指针运算
指针可以进行一些运算,如加法、减法运算。指针加法和减法的步长取决于指针所指向的数据类型的大小。例如:
int numbers[5] = {1, 2, 3, 4, 5};
int *ptr = numbers; // 指针指向数组首元素
ptr++; // 指针移动到下一个元素
std::cout << *ptr << std::endl; // 输出 2
3. 指针与数组
在 C++ 中,数组名实际上是一个指向数组首元素的指针常量。例如:
int numbers[5] = {1, 2, 3, 4, 5};
int *ptr = numbers;
通过指针可以像访问数组一样访问数组元素,例如:
std::cout << ptr[2] << std::endl; // 等价于 std::cout << numbers[2] << std::endl; 输出 3
4. 动态内存分配与指针
动态内存分配是指在程序运行时根据需要分配内存空间。C++ 中使用 new
运算符进行动态内存分配,使用 delete
运算符释放动态分配的内存。例如:
int *dynamicNum = new int;
*dynamicNum = 20;
std::cout << *dynamicNum << std::endl;
delete dynamicNum;
对于动态分配的数组,可以使用 new[]
和 delete[]
:
int *dynamicArray = new int[5];
for (int i = 0; i < 5; i++) {
dynamicArray[i] = i + 1;
}
delete[] dynamicArray;
如果忘记释放动态分配的内存,会导致内存泄漏,因此在使用动态内存分配时,一定要确保及时释放内存。
结构体与联合体
1. 结构体
结构体是一种自定义的数据类型,它可以将不同类型的数据组合在一起。定义结构体的语法如下:
struct Student {
std::string name;
int age;
float grade;
};
可以通过结构体类型定义变量,并访问其成员:
Student student1;
student1.name = "Alice";
student1.age = 20;
student1.grade = 3.5f;
结构体变量也可以在定义时初始化:
Student student2 = {"Bob", 21, 3.8f};
2. 联合体
联合体也是一种自定义数据类型,它允许不同类型的数据共享同一块内存空间。定义联合体的语法如下:
union Data {
int num;
float f;
char ch;
};
由于联合体成员共享内存,同一时间只能有一个成员有效。例如:
Data data;
data.num = 10;
std::cout << data.num << std::endl;
data.f = 3.14f;
std::cout << data.f << std::endl;
在上述代码中,当给 data.f
赋值后,data.num
的值就不再有效,因为它们共享同一块内存。
面向对象编程基础
1. 类与对象
类是一种用户自定义的数据类型,它封装了数据(成员变量)和操作这些数据的函数(成员函数)。定义类的语法如下:
class Circle {
private:
float radius;
public:
void setRadius(float r) {
radius = r;
}
float getRadius() {
return radius;
}
float calculateArea() {
return 3.14159f * radius * radius;
}
};
在类中,private
关键字修饰的成员变量只能在类内部访问,public
关键字修饰的成员函数可以在类外部访问。
对象是类的实例,通过类可以创建多个对象。例如:
Circle circle1;
circle1.setRadius(5.0f);
std::cout << "圆的半径: " << circle1.getRadius() << std::endl;
std::cout << "圆的面积: " << circle1.calculateArea() << std::endl;
2. 封装
封装是面向对象编程的一个重要特性,它将数据和操作数据的方法封装在一起,通过访问控制符(private
、public
、protected
)来限制对类成员的访问。这样可以隐藏类的内部实现细节,只向外部提供必要的接口,提高代码的安全性和可维护性。
3. 继承
继承允许一个类(子类)从另一个类(父类)获取属性和行为。子类可以继承父类的成员变量和成员函数,并可以根据需要进行扩展或重写。定义继承关系的语法如下:
class Shape {
protected:
std::string color;
public:
void setColor(std::string c) {
color = c;
}
std::string getColor() {
return color;
}
};
class Rectangle : public Shape {
private:
float width;
float height;
public:
void setDimensions(float w, float h) {
width = w;
height = h;
}
float calculateArea() {
return width * height;
}
};
在上述代码中,Rectangle
类继承自 Shape
类,Rectangle
类可以访问 Shape
类的 protected
成员变量 color
和 public
成员函数 setColor
和 getColor
。
4. 多态
多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。C++ 中实现多态主要通过虚函数和指针或引用。例如:
class Animal {
public:
virtual void makeSound() {
std::cout << "动物发出声音" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() override {
std::cout << "汪汪汪" << std::endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
std::cout << "喵喵喵" << std::endl;
}
};
void makeAnimalSound(Animal *animal) {
animal->makeSound();
}
在上述代码中,Animal
类中的 makeSound
函数被声明为虚函数,Dog
和 Cat
类重写了 makeSound
函数。通过 makeAnimalSound
函数,根据传入的对象类型不同,会调用不同类的 makeSound
函数,实现多态。
Animal *dog = new Dog();
Animal *cat = new Cat();
makeAnimalSound(dog);
makeAnimalSound(cat);
delete dog;
delete cat;
通过以上内容,你已经对 C++ 的基础知识有了较为全面的了解。在实际编程中,还需要不断练习和实践,才能熟练掌握 C++ 语言,开发出高效、可靠的程序。