文章目录
C++基本语言
语言特性:过程式、对象式程序设计
面向过程式的程序设计
C语言的编程风格是面向过程式的程序设计,即当编写代码解决一个问题时,代码的编写方法是从上到下,逐步求精,一些公用的功能写成函数,需要用到结构体时就定义结构体等。这属于按顺序一步一步把问题解决。
基于对象的程序设计和面向对象的程序设计
C++本身支持C语言风格的程序设计,同时也支持基于对象的程序设计和面向对象的程序设计。
把变量和函数封装到一个类中,定义一个类对象并通过该对象调用各种成员函数实现各种功能的程序书写方式,称为基于对象的程序设计。
当把继承性和多态性技术融入基于对象的程序设计中去时,即为面向对象的程序设计。
面向对象的程序设计的优点
- 易维护。每个类有不同的接口,每个接口的维护都在自己的类中进行,这就更清晰、更好维护了。
- 易扩展。通过继承性和多态性,可以少写很多代码,实现很多变化。
- 模块化。通过设置各种访问级别来限制别人的访问,同时也保护了数据的安全。
C++程序和项目文件构成
-
一个项目(工程)中可以包含多个
.cpp
文件和多个.h
头文件,一般.cpp
叫源文件,.h
叫头文件。一些公共的定义一般都会放在头文件中(如函数声明,一些类、结构的定义,一些#define
。 -
不同的C++编译器会使用不同的文件后缀名,如有
.c
、.cpp
、.cc
、.cxx
这些源文件后缀,这些后缀用来告诉编译器这是一个C++的源程序或者时C++的头文件。.cc
、.cxx
一般在GNU编译器上比较常见,此外,还有.m
、.mm
——如果在Mac OS苹果电脑上用Xcode
进行开发,它们用的是Objective-C
语言,但里面有时也会嵌入C或者C++代码。一般.m
就暗示代码含有Objective-C
和C语言语句,.mm
就暗示代码含有Objective-C
和C++语句。 -
C++语言的头文件扩展名一般以
.h
居多,此外,还有.hpp
。.hpp
一般来讲就是把定义和实现都包含在一个文件里,有一些公共开源库就是这也做的,主要是能有效地减少编译次数。
编译型语言概念与可移植性问题
C++本身是属于编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成二进制文件(可执行文件),执行的时候,不需要重新编译,直接使用编译结果就行了。
而解释型语言编写的程序不进行预先编译,以文本方式存储程序代码。但是在执行程序的时候,解释型语言必须先解释再执行。
编译型语言执行速度快,因为它不需要解释。
从编写程序的角度谈到可移植性,实际上是针对源代码而言的,相同的一份源代码,在Windows操作系统和Linux操作系统上都能够成功地编译和运行(如借助Visual Studio),且这份代码生成的可执行文件执行后能够实现相同的功能,那么这份源代码就被称为可移植的。
命名空间简介与基本输入/输出精解
命名空间简介
命名空间是为了防止名字冲突而引入的一种机制。系统中可以定义多个命名空间,每个命名空间都有自己的名字,不可以同名。可以把命名空间看成一个作用域,这个命名空间里定义的函数与另外一个命名空间里定义的函数,即便同名,也互不影响(因为命名空间名不同)。
-
命名空间定义:
namespace 命名空间名 { // ... }// 这里无需分号结尾
-
命名空间定义可以不连续,可以写在不同的位置,甚至写在不同的源文件中。如果以往没有定义该命名空间,那么就相当于定义了一个命名空间;如果以往已经定义该命名空间,那就相当于打开已经存在的命名空间并为其添加内容。
-
外界访问某个命名空间中的实体的方法:
命名空间名::实体名
,可以用using namespace 命名空间名
简化前缀的书写。
基本输入/输出
基本输出
C++中输入/输出用的是标准库iostream库(输入/输出流),流就是一个字符序列。
std::cout << "很高兴大家一起学习C++" << std::endl;
std
:这是标准库中定义的一个命名空间。cout
:是一个对象,一个与iostream相关的对象,cout
对象被称为“标准输出”,一般用于向屏幕输出一些内容。- <<:输出运算符,表示将“<<”右侧的内容写到
cout
中去。 std::endl
是一个函数模板名,相当于函数指针。std::endl
一般都在语句的末尾,有两个作用。- 输出换行符
\n
。 - 刷新输出缓冲区,调用
flush
强制输出缓冲区中所有数据(也叫刷新输出流,目的就是显示到屏幕),然后把缓冲区中数据清除。
- 输出换行符
什么叫输出缓冲区?可以理解成一段内存,使用std::cout
输出的时候实际上是往输出缓冲区中输出内容。那么输出缓冲区什么时候把内容输出到屏幕上呢?有如下几种情况:
- 缓冲区满了。
- 程序执行到main函数中的return,要正常结束了。
- 使用
std::endl
了,因为使用后会调用flush()
。 - 系统不太忙的时候,会查看缓冲区内容,发现新内容就正常输出。所以有时使用
std::cout
时,语句行末尾是否增加std::endl
都能将信息正常且立即输出到屏幕。
为什么要有这个输出缓冲区?用std::cout
直接输出信息到屏幕时,缓冲区的作用体现的不太明显,那如果是输出信息到一个文件中,那么输出缓冲区作用就明显多了,总不能输出一个字符,就写一次文件,因为文件时保存在硬盘上,速度和内存相比实在时慢太多了,所以很有必要将数据临时保存到输出缓冲区,然后一次性地将这些数据写入硬盘。
基本输入
int value1 = 0, value2 = 0;
std::cin >> value1 >> value2; // 输入多个值之间用空格分开
cin
也是一个iostream对象,被称为“标准输入”。>>
:输入运算符,表示把值传递给变量。
auto、头文件防卫、constexpr
auto关键字简介
auto关键字在C++98中就已经存在,但是在C++11中,auto被赋予了全新的含义——变量的自动类型推断。
auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型。
auto bvalue = true;
auto ch = 'a';
auto dv = 1.2;
auto iv = 5;
有些类型名很长,如后面要学习到的泛型,那么使用auto就能避免书写很长的类型名。
auto的自动类型推断发生在编译期,所以使用auto并不会造成程序运行时效率的降低。
头文件防卫式声明
-
头文件
head.h
中有如下定义:int g_globalh1 = 8;
-
头文件
head2.h
中有如下定义:int g_globalh2 = 5;
-
在主源文件(
MyProject.cpp
)中需要用这两个全局变量#include "head.h" #include "head2.h" int main() { cout << g_globalh1 << endl; // 8 cout << g_globalh2 << endl; // 5 }
执行上面这段代码,目前为止并没有什么问题。
-
随着项目的增大,需要定义更多复杂的数据类型,假如现在因为一些原因需要在头文件
head2.h
中包含头文件head.h
,于是head2.h
内容修改如下:#include "head.h" int g_globalh2 = 5;
此时编译,出现重定义。
constexpr 关键字
constexpr
是C++11引入的关键字,也代表一个常量的概念,意思是在编译的时候求其值,所以能够提升运行时的性能。编译阶段就知道值可以更利于做一些系统优化工作等。
constexpr int func1(int abc)
{
abc = 16;
int a3 = 5;
return abc * a3;
}
int main()
{
constexpr int var1 = 1;
constexpr int var2 = 11 * func1(12);
cout << var2 << endl;
return 0;
}
书写func1
函数时必须小心,其中的代码尽可能简单。而且,某些代码出现在func1
函数中还会导致编译无法通过。例如,在func1
函数中定义一个未初始化的变量就会导致编译出错。
int unvar; // 编译时会引发错误,必须在定义的时候初始化
范围for、new内存动态分配与nullptr
范围for语句
C++11中引入了范围for语句,用于遍历一个序列。
int v[]{ 12, 13, 14, 16, 18 };
for (auto x : v)
{// 数组v中每个元素依次放入x并打印x的值。相当于把v的每个元素值复制到x中,然后打印
cout << x << endl;
}
for (auto x : { 11, 34 ,56,21,34,34 })
{// {}中是一个元素序列,范围for就是应用于任意的这种元素序列
cout << x << endl;
}
上面示例中第一个for语句可以改成for (auto& x : v)
使用引用的方式,避免数据的复制动作,提高程序运行效率。
一般来讲,一个容器只要其内部支持begin
和end
成员函数用于返回一个迭代器,能够指向容器的第一个元素和末端元素的后面,这种容器就可以支持范围for语句。
动态内存分配问题
在C++中,一般把内存分成5个区域:
- 栈。函数内的局部变量一般都在这里创建,由编译器自动分配和释放。
- 堆。由程序员使用
malloc
/new
申请,free
/delete
释放,申请并使用完毕后要及时释放以节省系统资源,防止资源耗尽导致程序崩溃。如果程序员忘记释放,程序结束时会由操作系统回收这些内存。 - 全局/静态存储区。全局变量和静态变量放这里,程序结束时释放。
- 常量存储区。存放常量,不允许被修改,如用双引号包含起来的字符串。
- 程序代码区。
堆和栈的区别:
- 栈空间有限(这是系统规定的),使用便捷。例如代码行
int a = 4;
,系统就自动分配了一个4字节给变量a使用。分配速度快,程序员控制不了它的分配和释放。 - 堆空间是程序员自由决定所分配的内存大小,大小理论上只要不超出实际拥有的物理内存即可,分配速度相对较慢,非常灵活。
malloc 和 free
在C语言中,malloc(动态内存分配)和free是系统提供的函数,成对使用,用于从堆中分配和释放内存。一般形式为:
void* malloc(int NumBytes);
malloc向系统申请分配指定NumBytes
字节的内存空间。返回类型是void*
类型,void*
表示未确定类型的指针。
C/C++规定,void*
类型可以强制转换为任何其他类型的指针。如果分配成功则返回指向被分配内存的指针,如果分配失败则返回空指针NULL
。
分配成功且当内存不再使用时,应使用free()
函数将内存释放。一般形式为:
void free(void* Ptr);
该函数将之前用malloc分配的内存空间还给程序或者操作系统,这样这块内存就被系统回收并在需要的时候由系统自由分配出去再使用。
int* p = NULL;
p = (int*)malloc(10 * sizeof(int)); // 分配了40字节
if (p != NULL)
{
*p = 5; // 这种写法其实只会用到分配的40字节中的4字节
cout << *p << endl;
free(p);
}
new 和 delete
C++中使用new和delete从堆中分配和释放内存,两者成对使用。
new一般使用格式有如下几种:
指针变量名 = new 类型标识符;
// 开辟一个存放整数的存储空间,返回一个指向该存储空间的地址,将一个int类型的地址给整型指针myint
int* myint = new int;
if (myint != NULL) // 其实如果new失败可能不会返回NULL,而是直接报异常
{
*myint = 8; // *myint代表指针指向的变量
delete myint; // 释放
}
指针变量名 = new 类型标识符(初始值);
int* myint = new int(18); // 分配内存同时将该内存空间的内容设置为18
if (myint != NULL)
{
*myint = 8; // *myint代表指针指向的变量
cout << *myint << endl; // 8
delete myint; // 释放
}
指针变量名 = new 类型标识符[内存单元个数];
int* a = new int[100]; // 开辟一个大小为100的整型数组空间
if (a != NULL)
{
int* p = a;
*p++ = 12;
*p++ = 18;
cout << *a << endl; // 12
cout << *(a + 1) << endl; // 18
// new时用了[],delete时就要用[],否则回收的内存就是第一个数组元素空间而不是整个数组
// []内不用写数组元素个数,系统有办法知道这个数为大小,写了数字也会被系统忽略
delete[12] a; // 释放int数组空间
}
malloc/free与new/delete的区别:
new不但分配内存,还会额外做一些初始化工作,而delete不但释放内存,还会额外做一些清理工作。
nullptr
nullptr
是C++11引入的新关键字,代表“空指针”。
cout << typeid(NULL).name() << endl; // int
cout << typeid(nullptr).name() << endl; // std::nullptr_t
上面范例中,通过结果可以看到,NULL
和nullptr
两者的类型是不同的。
在后面学习函数重载时,因为NULL
和nullptr
类型不同,所以如果把这两者当函数实参传递到函数中去,则会导致因为实参类型不同而调用不同的重载函数。
void myfunc(void* ptmp)
{
cout << "void myfunc(void* ptmp)" << endl;
}
void myfunc(int tmpvalue)
{
cout << "void myfunc(int tmpvalue)" << endl;
}
int main()
{
myfunc(NULL); // 调用void myfunc(int tmpvalue)
myfunc(nullptr); // 调用void myfunc(void* ptmp)
return 0;
}
- 对于指针的初始化,能用
nullptr
的全部用nullptr
。 - 以往用到的与指针有关的
NULL
的场合,能用nullptr
取代的全部用nullptr
取代。