MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++入门与基础

2022-02-277.5k 阅读

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 的取值范围是 -21474836482147483647

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.10.2 在二进制表示中是无限循环小数,存储时会有精度损失。

3. 布尔类型

  • bool:布尔类型,占用 1 个字节,只有两个取值 truefalse,用于表示逻辑值。例如:
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;
}
  • 块作用域:在一对花括号 {} 内声明的变量具有块作用域,例如 ifforwhile 语句块中的变量。

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. 比较运算符

比较运算符用于比较两个值的大小关系,结果为 truefalse。常见的比较运算符有 ==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)和 <=(小于等于)。例如:

int a = 10;
int b = 20;
bool isGreater = a > b;

4. 逻辑运算符

逻辑运算符用于组合多个逻辑表达式,结果也是 truefalse。主要的逻辑运算符有 &&(逻辑与)、||(逻辑或)和 !(逻辑非)。例如:

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 后继续执行下一个 casedefault 分支在表达式的值与所有 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; // 定义一个指向整型变量 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. 封装

封装是面向对象编程的一个重要特性,它将数据和操作数据的方法封装在一起,通过访问控制符(privatepublicprotected)来限制对类成员的访问。这样可以隐藏类的内部实现细节,只向外部提供必要的接口,提高代码的安全性和可维护性。

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 成员变量 colorpublic 成员函数 setColorgetColor

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 函数被声明为虚函数,DogCat 类重写了 makeSound 函数。通过 makeAnimalSound 函数,根据传入的对象类型不同,会调用不同类的 makeSound 函数,实现多态。

Animal *dog = new Dog();
Animal *cat = new Cat();
makeAnimalSound(dog);
makeAnimalSound(cat);
delete dog;
delete cat;

通过以上内容,你已经对 C++ 的基础知识有了较为全面的了解。在实际编程中,还需要不断练习和实践,才能熟练掌握 C++ 语言,开发出高效、可靠的程序。