前言
对于刚学习完c++不久的我们,心中会有诸多疑惑,但最多的肯定是它能做什么。觉得自己好像学会了,但又好像没学会。不会实际应用,只会基础语法,不能把语法连在一起使用。如果你也有个问题,不妨来看看下面的内容
通过学习c++程序设计基础,我们知道,这里面大致讲了如下内容:
- 基础数据类型
- 类型转换
- 常量
- 变量
- 运算符
- 输入输出
- 分支判断
- 循环
- 函数
- 自定义数据类型和取别名的关键字typedef
- 模板
- 结构体
- 指针
- 动态分配
- 类
- 关键字const 、static、extern
- 预处理#define
对于上面的内容我们需要知道每一个是什么,它有什么作用。开始正文之前我们要清楚一个事情,一个程序是由数据结构 + 算法组成。数据结构负责存储数据,算法负责对这些数据操作。计算机处理的对象是数据,而数据是以某种特定的形式存在,数据结构指的是数据组织方式(数组、链表)
基础数据类型
对于基础数据类型我们需要知道有哪些以及占用的内存大小(如int 4个字节),这里的四个字节并不是指int类型占用4个字节,而是指存储一个int类型数据需要用到4个字节内存。这也就是为什么后面我们声明一个int变量a,a的内存为四个字节。或者new int,其指向的内存为四个字节
类型转换
这个行为主要发生在赋值运算符两侧类型不一致的情况下,如果能隐式转换,则不需要额外处理,如果不能隐式转换则需要我们强制类型转换,在转换的过程中因为不同类型数据内存大小不同、每个字节含义不同等,会存在溢出的问题,所以我们需要知道每种基础类型溢出的结果是什么。
常量
常量的值是不能改变的,它包含两大类,数值型常量、字符型常量
常量包含,字符常量,字符串常量、符号常量、数值常量。
用''包着的就是字符常量, 但有一点需要注意,某些字符常量是可以被转义用来代表其它含义如'\n'(换行符),
用""包着的就是字符串常量,
关于数值常量就是给定一串数字,可以是小数也可以是整数(如0、12.5等)。
至于符号常量则是通过#define定义如下:
#define PRICE 3
int main(){
int num = 10;
int total = num * PRICE;
return 0;
}
这里的常量3虽然有名字PRICE,但由于其在编译预处理的时候PRICE被替换成了3,正式编译的时候就不会有PRICE了。
变量
变量是在程序运行中值可以改变量。它有一个名字并且在内存中占用一定存储单元。如下:
由上图我们可以看到,变量名代表内存中的存储单元,存储单元里面存储变量的值。并且我们可以联想一下如果这个a变量是int类型,那么这个存储单元肯定就是4个字节吧(默认32位系统)。这里的变量值3肯定是通过赋值运算符号得到的吧。
除了变量的含义,我们还要知道如何定义一个变量,并且遵循先定义后使用的 原则去给某个变量赋值等操作。
运算符
对于运算符,我们需要知道有哪些运算符以及它的优先级与结合性,这样才能让我们在多个运算符计算一个值的时候才能有把握的知道哪些运算符先计算哪些后计算,最终得到一个想要的值,实在忘记的情况下,我们可以把某些运算符的运算先用小括号括起来。
输入输出
在C语言中输入和输出的功能是通过调用scanf函数和printf 函数来实现的在C++中是通过调用输入输出流库中的流对象cin和cout实现的。也就是说,输人输出不是由 C++本身定义的,而是在编译系统提供的 IO库中定义的。
C++的输出和输人是用“流”(stream)的方式实现的。“流”指的是来自设备或传给设备的一个数据流。数据流是由一系列字节组成的,这些字节是按进人“流”的顺序排列的。cout是输出流对象的名字cin是输入流对象的名字“<<”是流插入运算符(也可称流插人操作符),其作用是将需要输出的内容插人到输出流中,默认的输入设备是显示器。“>>”是流提取运算符,其作用是从默认的输入设备(一般为键盘)的输人流中提取。
分支判断
分支判断是通过表达式的结果决定执行哪条分支语句,通常其行为为:
我们学过的分支判断包含if、if-else、if-else if、if-else if -else、switch-case。这些需要我们熟练掌握如何使用,以便在处理某些条件的情况下正确选择。
循环
在人们所要处理的问题中常常遇到需要反复执行某一操作。例如,要输人100个学生的成绩;求50个数之和;迭代求根等。这就需要用到循环控制。许多应用程序都包含循环。顺序结构、选择结构和循环结构是结构化程序设计的3种基本结构,是各种复杂程序的基本构造单元。因此程序设计者必须熟练掌握选择结构和循环结构的概念及使用。
循环我们要想到的有for、while以及do while。以及在循环体内部,我们可以使用的关键字break、continue以及return来控制是否执行本次循环以及是否退出当前循环或者函数。
函数
在C和C++中函数是程序的重要组成部分每个程序都必须有一个主函数(main函数)。主函数将被第一个执行的函数,主函数结束意味着程序结束
“函数”这个名词是从英文 funtion 翻译过来的,其实 function的原意是“功能”。顾名思义,一个函数就是一个功能。
在程序运行过程中,由主函数调用其他函数,其他函数也可以互相调用。在C语言中没有类和对象,在程序模块中直接定义函数。C 程序的主要部分是函数,C 语言被认为是面向函数的语言。C++ 基于过程的程序设计沿用了C 语言使用函数的方法(本章介绍的就是这种方法)。在 C++ 面向对象的程序设计中,主函数以外的函数大多是被封装在类中的。主函数或其他函数可以通过类对象调用类中的函数。
自定义数据类型和取别名的关键字typedef
顾名思义,typedef是c++中的关键字,可以自定义类型以及给已有的类型取别名。
自定义类型用法typedef + 新类型(如定义一个函数指针类型 typedef void(*Fun)(void), 这里定义了一个函数指针类型Fun,他指向函数返回值为void,形参为void的函数)
取别名用法 typedef + 已有类型 + 别名(如将int取别名为int32, typedef int int32)
模板
模板只要用于框架设计,它泛化了类型,实现某个功能时,不关心其具体类型。当然,模板也可以做一些约束条件,让其在编译阶段识别到某些特定错误。
定义模板关键字templete, 关于模板我们需要知道函数模板、类模板、类函数模板等。学会这些,我们可以尝试模板特化偏特化以及模板的条件约束。
结构体
在一个组合项中包含若干个类型不同(当然也可以相同)的数据项。C和 C++ 允许用户自己指定这样一种数据类型它称为结构体。
结构体关键字为struct, 声明一个结构体的方式为struct + 结构体名称 + {各种类型变量}。在老的c语言标准中,定义一个结构体变量需要加上struct, 如结构体名称为Student的结构体变量定义
struct Student{
double score;
int height;
}; // 声明一
int main(){
struct Student st; // 定义一
}
所以我们经常看到这样的结构体声明
typedef struct _Student{
double score;
int height;
} Student; // 声明二
int main(){
Student st; // 定义二
}
上面使用了一个关键字typedef,由于前面已经讲过,这里就不重述了,但在新的标准或者c++中,定义一个结构体变量是不需要加上struct关键字的,所以我们可以采用第一种声明方式,使用第二种变量定义方式
指针
讲指针前,我们要复习一下变量的东西,一个变量包含三部分变量名、变量值、存储单元。所以一个指针变量同样包含这三部分。
什么是指针呢?我们知道,指针是一种特殊的变量类型,它存储了一个内存地址,该地址指向另一个变量的位置。通过指针,可以直接访问和操作内存中的数据。
既然指针也是变量,那么它也涉及到定义、初始化、赋值等操作。
定义一个指针:
int* ptr; // 定义一个指向int类型变量的指针
指针的初始化:
int value = 42;
int* ptr = &value; // 将ptr指向value的地址
通过指针访问变量:
int value = 42;
int* ptr = &value;
cout << *ptr; // 输出指针指向的值,即42
修改指针指向的值:
int value = 42;
int* ptr = &value;
*ptr = 24; // 修改指针指向的值为24
cout << value; // 输出24
进阶:
我们可以看看ptr变量值是什么
通过内存我们发现,当我们将ptr指向value的时候,ptr变量值为value的地址。那么我们思考一下,这个问题。
template <typename T>
void Malloc(T ptr) {
// 分配一段连续的int空间
// 让main函数中的ptr指向这段连续空间
}
int main() {
int* ptr = nullptr;
// 调用Malloc, 让ptr指向一段堆空间
if (!ptr) {
// 输出分配的新空间的值
delete[] ptr;
}
return 0;
}
这个的Malloc形参类型应该是什么呢。
通过看ptr变量值我们获得到的结论,我们只需要将ptr变量的存储单元地址传递到Malloc,然后Malloc将这段存储单元的值修改为新申请的内存地址值就能完成我们想要实现的功能了,如下
void Malloc(int** ptr) {
*ptr = new int[100];
}
int main() {
int* ptr = nullptr;
// 调用Malloc, 让ptr指向一段堆空间
Malloc(&ptr);
if (!ptr) {
// 输出分配的新空间的值
delete[] ptr;
}
return 0;
}
指针博大精深,想要弄懂并学以致用还是需要不断学习完善自己的不足。
动态分配
在软件开发中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数 malloc 和 free 来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符 new 和 delete 来取代 maloc 和 free 函数。注意:new 和delete 是运算符,不是函数,因此执行效率高。虽然为了与C语言兼容,C++仍保留malloc 和 free 函数但建议用户不用 malloc 和 free 函数而用 new和 delete 运算符
new就是申请内存,如果有构造函数,回去调用构造函数。所以在使用此运算符的时候我们可以给变量赋初值。
delete就是释放内存,用于释放new申请的内存。如果new申请的内存为数组,那么释放就要使用delete[]
类
类是 C++中十分重要的概念,它是实现面向对象程序设计的基础。C++对C的改进,最重要的就是增加了“类”这样一种类型。所以C++开始时被称为“带类的C”。类是所有面向对象的语言的共同特征,所有面向对象的语言都提供了这种类型。如果一种计算机语言中不包含类,它就不能称为面向对象的语言。一个有一定规模的C++程序是由许多类所构成的。可以说,类是C++的灵魂如果不真正掌握类,就不能真正掌握C++
基于对象就是基于类。与基于过程的程序不同,基于对象的程序是以类和对象为基础的.程序的操作是围绕对象进行的。在此基础上利用了继承机制和多态性,就是面向对象的程序设计。
凡是以类对象为基本构成单位的程序称为基于对象的程序。而面向对象程序设计则还有更多的要求。面向对象程序设计有4个主要特点:抽象封装继承和多态性。C++的类对象体现了抽象和封装的特性,在此基础上再利用继承和多态性就成为真正的面向对象的程序设计
关于类的知识点有权限、基础、多态以及有缘函数,比较重要的是多态部分,多态涉及到虚函数、虚函数表、虚函数指针等。虚函数表在编译时生成,所有类实列共享此虚函数表,虚函数指针在类实例化时指向虚函数表。虚函数表指针被编译器安插在类里面。
class base {
public:
virtual ~base() = default;
virtual void fun() { std::cout << "base" << std::endl; }
};
class derived:public base {
public:
void fun() { std::cout << "derived" << std::endl; }
private:
int data = 0;
};
int main() {
derived b;
return 0;
}
代码如上, 我们看看编译后的d内存分布,可以看到d有一个_vfptr和一个data变量,_vfptr就是我们口中的虚函数表指针,它指向了一个虚函数表,表里面存放了两个函数地址。这也是为什么我们可以运行时知道该运行哪个函数
关键字const 、static、extern
const关键字用于声明常量。它可以用于修饰变量,表示该变量的值在初始化后不能再被修改。const还可以用于修饰函数,表示该函数不会修改任何类成员变量。使用const的好处是可以提高代码的可读性和安全性。
使用const时需要注意以下几点:
声明时必须进行初始化,否则会导致编译错误。
const对象只能调用const成员函数,因为非const成员函数可能会修改对象的状态。
const对象不能调用非const成员函数指针,因为这可能会导致非const成员函数修改const对象的状态。
static关键字有多种用法,它可以用于修饰变量和函数。
修饰变量:当static用于修饰局部变量时,表示该变量在函数调用之间保持持久性,即该变量的生存期与程序的生存期相同。当static用于修饰全局变量时,表示该变量的作用域仅限于定义它的源文件,其他文件无法访问该变量。
修饰函数:当static用于修饰全局函数时,表示该函数的作用域仅限于定义它的源文件,其他文件无法调用该函数。当static用于修饰类的成员函数或成员变量时,表示该函数或变量属于类本身,而不是类的实例。这样的成员函数或成员变量可以在没有类的实例的情况下访问。
使用static时需要注意以下几点:
静态变量在程序运行期间一直存在,并且只能在定义它的函数或文件中访问。
静态函数只能访问静态变量和其他静态函数。
extern关键字用于声明外部变量或函数。当在一个源文件中使用extern关键字来声明一个变量或函数时,它表示该变量或函数是在其他源文件中定义的。这样可以使得多个源文件共享同一个全局变量或函数。
使用extern时需要注意以下几点:
声明变量时,extern关键字可以省略,因为变量默认就是外部链接的。但是如果要在一个源文件中访问其他源文件中定义的全局变量,就需要使用extern关键字进行声明。
声明函数时,extern关键字可以省略,因为函数默认就是外部链接的。但是如果要在一个源文件中调用其他源文件中定义的函数,就需要使用extern关键字进行声明。
预处理#define
#define是C++中的预处理指令,用于创建宏定义。它可以用来定义常量、函数宏以及条件编译。
定义常量:
#define PI 3.14159
这样可以将PI定义为一个常量,将在编译过程中将所有出现的PI替换为3.14159。
定义函数宏:
#define SQUARE(x) ((x) * (x))
这样可以定义一个函数宏SQUARE,它会将传入的参数平方并返回结果。在编译过程中,所有出现的SQUARE(x)将被替换为((x) * (x))。
需要注意的是,函数宏并不是真正的函数,而是在编译前进行简单的文本替换。因此,它没有函数调用的开销,但也可能带来一些意想不到的副作用。
条件编译:
cpp
Copy
#define DEBUG
#ifdef DEBUG
// 调试模式下的代码
#else
// 正常模式下的代码
#endif
使用#define可以定义一个名为DEBUG的宏,根据宏的定义与否,可以在编译时选择不同的代码路径。在上述示例中,如果定义了DEBUG宏,那么编译器将编译调试模式下的代码;否则,编译器将编译正常模式下的代码。
需要注意的是,宏定义没有类型检查,而且在替换过程中可能会带来一些意想不到的错误,因此在使用宏定义时需要谨慎。推荐使用常量或枚举类型来代替宏定义,以提高代码的可读性和安全性。