01 C++简介
C++是一门以C为基础,发展而来的面向对象的程序设计语言,C++完全兼容C
C++ =====> C plus plus C# =====> C plus plus plus plus
1983 由Bjarne(比扬尼)在贝尔实验室创建 C with class
1984 改名为C++ C plus plus
1987 g++编辑器
1998 C++98(*)国际标准发布,称为C++ISO98,以后每5年更新一次标准
2003 C++03
2011 C++11(*)次标准对C++语言的影响很大,称为现代C++(新标准) <-----
2014 C++14
2017 C++17
2020 C++202023 C++23
...
编译器:GUN/Linux(linux的全称) 平台使用g++编译器,如果系统没有预装,则需要自己安装
g++ -v // 用于查看g++编译器的版本信息以及它所使用的配置详情
sudo apt-get install g++ // 前提:有网络
ARM平台(板子是没有编辑器的),如果程序需要放到开发板上面运行,则需要使用交叉编辑器 —— arm-linux-g++
要求:版本是和开发板匹配的
如果没有,参考交叉开发的内容,自己配置交叉编译器
windows中也可以编译:
1. 使用g++的windows版本,称为minGW
2. 使用一些集成开发环境IDE(VS,VC++6.0,devC++,codeblocks ......)
有的时候我们的语法没有问题,但是直接编译是过不去的
这个时候就需要加上11标准,有些是需要加上14标准的
建议直接加11标准进行编译
g++ main.cpp -o main -std=c++11
组成:语言本身(各种各样的基础语法)
库(标准库(STL:算法 / 容器 / 迭代器) 和 第三方库)
02 第一个C++程序
#include <iostream> // C++头文件,用于支持基本的输入输出功能 // cout 和 cin // #include <stdio.h> // printf 和 scanf C++全面的兼容C语言 int main() { // printf("hello world\n"); // 输出到终端 // 必须添加头文件 #include <stdio.h> std::cout << "hello world" << std::endl; // 输出内容到标准输出 int a, b; // scanf("%d%d", &a, &b); // 从标准输入获取两个整数 // 必须添加头文件 #include <stdio.h> std::cin >> a >> b; // 从标准输入获取两个整数 // std::cout << a << "+" << b << "=" << a + b << "\n"; std::cout << a << "+" << b << "=" << a + b << std::endl; std::cout << &a << std::endl; std::cout << &b << std::endl; // std::cout << "hahaha\n"; std::cout << "hahaha" << std::endl; return 0; }
程序由两个部分组成:分别是头文件(#include)和程序的主函数(main)
对比于C语言程序,有哪些地方不一样?
<< (c-out)
在C语言中是左移运算符,在C++中依然具有左移的功能,但是我们又给它添加了新的功能(运算符重载),和 cout 这个变量放在一起,用于把数据输出到标准输出,也称为输出运算符
cout ----> console output 控制台(命令行)输出 -----> stdout
C++是以C语言为基础的,所以C++全面的兼容C语言(C中可以用的在C++中基本上都可以使用)cin 表示标准输入,即stdin
cout 表示标准输出,即stdout
clog 表示标准错误,带缓冲的标准错误
cerr 表示标准错误,即stderr,不带缓冲的标准错误
在C++中,一切都是对象(拥有行为和属性),可以设置输出对象(cout)的属性
如:输出的整数的进制,用 hex 表示16进制,使用 oct 表示八进制,使用dec表示十进制std::cout << std::hex; // 设置输出流对象(整数)的格式为16进制 std::cout << std::oct; // 设置输出流对象(整数)的格式为8进制 std::cout << std::dec; // 设置输出流对象(整数)的格式为10进制
>> (c-in)
在C语言中是右移运算符,在C++中依然具有右移的功能,但是我们又给它添加了新的功能(运算符重载),和 cin 这个变量放在一起,用于输入,也称为输入运算符
cin ----> console input 控制台(命令行)输入 -----> stdin
头文件:原理和C语言一样iostream,就是一个被包含的头文件名称
/usr/include/c++/7.4.0/在原来的C语言中,头文件都是以xxx.h的形式存在的
在C++中,头文件只有名字,没有后缀(.h)如:iostream
自己写的头文件代码可以添加后缀(.h、.hpp)
原有的C体系中的部分头文件仍然以.h的形式存在,还有部分头文件被升级到C++体系中,在原有的名字前面加一个字符c,把后面的.h去掉了
如:stdio.h ------> cstdio
string.h ------> cstring
在C++中仍然可以使用C语言中的所有头文件和头文件中的函数
后缀名.cpp [.C .cxx]
编译器g++
:: 作用域运算符std::cout std就是一个作用域的名字,是单词"standard"的缩写,是标准C++库中的一个作用域名称
这个作用域(std)中定义了标准库中所有的名字,我们把这种专门用来定义名字的作用域称为名字空间(namespace)
也就是说,std::cout,指的是 cout 这个名字来源于 std 这个名字空间
注意:c++里面权限只能降不能升 // char *p = "hello world"; // 错误 const char *p = "hello world"; // 正确
03 名字空间(namespace)
为什么需要名字空间呢?
名字空间是ANSIC++引入的可以由用户命名的作用域
本质:由程序设计者命名的内存区域
作用:用于存放各种各样的名称(变量名/类型名...),达到避免名字冲突的目的
定义方法:namespace 是定义名字空间的关键字 =====> namespace 名字空间的名字
如:
namespace ns1
{各种名字(变量名/函数名/类型名......)
......
namespace ns2 // 嵌套定义名字空间
{
各种名字
......
};
};
其中,ns1 就是我们自己定义的名字空间名称,如果我们需要使用名字空间中的对象名,则需要加作用域
可以包括:
变量名
函数名
常量
结构体
类型名
模板
嵌套名字空间
......
使用方法:先定义再使用1. 使用作用域运算符
名字空间名称::要访问的名字
如:
std::cout2. 名字空间指令(using 指令)
using namespace 名字空间名称;如:
using namespace std;
就是把指定的名字空间中的所有的名字都导入到当前的作用域
std::cout ------> cout
using namespace Ns1;
把Ns1中所有的名字都导入到当前作用域
就可以像使用普通变量一样使用名字空间中的名字了
3. 名字空间声明(using 声明)
using 名字空间名称::名字;如:
using std::cout;
就是把名字空间中指定的名字导入到当前的作用域
一般来说,使用using声明,比使用using指令更加安全,这是因为
using声明只会引入指定的名称
using指令容易造成"名字污染"
可以使用名字空间别名 -----> 为名字空间取一个别名(namespace alias),一般是用来代替较长的名字空间名称
如:
namespace Television {
.....};
namespace TV = Television;// 别名TV就和原名等价
无名名字空间C++编译器会自动的把不属于任何名字空间的全局标识符(全局变量,全局函数名,全局类型名...)都放到无名名字空间
使用无名名字空间中的名字:::要访问的名字
#include <iostream> using namespace std; namespace Ns1 { int a = 100; void show() { cout << "hello" << endl; } namespace Ns2 { int a = 101; }; }; // using namespace Ns1; int a = 102; int main() { int a = 103; Ns1::show(); // hello cout << Ns1::a << endl; // 100 std::cout << Ns1::Ns2::a << std::endl; // 101 cout << a << endl; // 103 cout << ::a << endl; // 访问无名名字空间中的a 102 return 0; }
04 C++中的函数
普通的函数定义方式:声明 + 定义!!!
返回值类型 函数名(形式参数类型列表)
{
函数体;(函数的实现)
}
调用函数:
函数名(实际参数列表);
(1) 默认参数(default argument)在C++中,编写函数的方式和C语言差别不大,但是可以给函数的形式参数设置默认值
int sum(int a, int b) { return a + b; } 可以给 sum 函数的形式参数(a,b)设置默认值 ======> int sum(int a = 10, int b = 10) { return a + b; } 当我们调用带有默认参数的函数时,如果提供实际参数,则使用提供的值 如果没有提供实际参数,就使用默认值 用法: 直接在函数定义的时候,把形式参数的默认值写在形式参数后面 如: int sum(int a = 10, int b = 10); cout << sum() << endl; // 以a=10,b=10匹配上面的函数 cout << sum(1) << endl; // 以a=1,b=10匹配上面的函数
注意:
1) 靠右原则
在定义时,如果某一个参数设置了默认值,那么这个参数的右边的所有参数都应该设置默认值,不能跳着传参,可以所有的参数都设置成默认值
拥有默认参数的形参,都放在右边
int sum(int a = 10, int b); // ERROR
没有设置默认值的参数,实参必须要传,设置了默认值的参数根据我们的实际情况可传也可不传,实参和形参一定是从左往右依次结合
2) 互斥原理
在函数的声明和定义分开的时候,只能把默认参数设置到声明的地方,定义的地方不能有默认参数
练习: 写一个函数,实现输出一个整型数组,在打印的时候,元素之间默认以空格隔开, 也可以指定其他的字符隔开 int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 1 2 3 4 5 6 7 8 9 10 -------------------------------------------------------------------- #include <iostream> using namespace std; void print_array(int *a, int n, char x = ' ') { for (int i = 0; i < n; i++) { cout << a[i]; if (i != n - 1) { cout << x; } } cout << endl; } int main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; print_array(a, 10); print_array(a, 10, '#'); return 0; }
(2) 函数重载(overload)
重载就是重新装载的意思,即同一个函数名能够完成不同的功能(同一个函数名可能存在多个定义)
函数重载允许使用同一个函数名定义多个不同的函数
编译的时候会根据实际提供给函数的参数(数量,类型)去匹配不同的函数
程序员只需要记住一个函数名,就可以完成一系列相关的任务
在C++中,函数重载的条件
1)在相同的作用域
2)函数名相同(不受返回值类型的影响)
3)函数的参数列表不同a. 参数的数量和类型
b. const修饰的自定义参数类型也算不同的类型
同时满足上面三个条件的函数,是重载关系
注意:函数重载和默认参数不冲突
int sum(int a = 0, int b = 0, int c = 0);
int sum(int a, int b);
======>
sum(1, 2); // 产生二义性
sum(1, 2, 3);
sum();
编译器在编译的时候,不知道是以(1, 2, 0)为参数调用第一个函数还是以(1, 2)调用第二个函数。同样,将编译器设计为这种情况优先调用某一种也是不合理的,具体调用同名函数的哪一个版本,在编译的时候就确定了
难道编译器真的允许函数重名吗?
肯定不允许,如果真的有同名函数,调用同名函数的时候,编译器就不知道应该调用哪一个了,编译器是根据函数调用语句中实际参数个数和类型来判断应该调用哪一个函数
原理:编译器换名机制
在C++中,重载函数经过编译器编译之后,编译器会整合参数的类型,形成一个新的函数名(符号),在调用函数的时候,编译器会推导实际参数的类型,也形成一个新的函数名,最终自动匹配相同的函数名
我们称这种机制为编译器换名机制!!!
g++ -c 1.cpp -o 1.o 把1.cpp编译成二进制文件(不能执行)
nm 1.o 看到的函数名都是被换名之后的名字
g++编译器在编译的时候,会把函数的参数类型和个数整合形成一个新的函数名(定义函数和调用函数的时候都会换名)
gcc编译器在编译的时候,不会对函数名进行任何处理
(3) C/C++混合编程
C++在C的基础上发展而来的,是全面的兼容C语言的语法(也可以利用C语言的所有成果)
出现的问题主要在编译和链接的时候
nb.h ---> inc
#ifndef __NB_H__ #define __NB_H__ // 类型的定义和函数的声明 void print_array(int *a, int n); #endif
nb.c ---> src
#include "nb.h" #include <stdio.h> void print_array(int *a, int n) { printf("I am NB function!\n"); for (int i = 0; i < n; i++) { printf("%d ", a[i]); } printf("\n"); }
01main.cpp ---> src
#include "nb.h" int main() { int a[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0}; print_array(a, 10); return 0; }
02main.c ---> src
#include "nb.h" int main() { int a[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0}; print_array(a, 10); return 0; }
动态库:
(arm-linux-)gcc -shared -fpic -o libxxx.so xxx.c xxx.c
-I头文件路径
编译器名称 共享库 与位置无关的 output 生成的库的名字 源文件列表在lib目录下:gcc -shared -fpic -o libnb.so ../src/nb.c -
I
../inc/(arm-linux-)gcc xxx.c -o xxx(可执行文件) -I头文件路径 -l库名 -L库文件路径
gcc 02main.c -o 02main -I../inc -lnb -L../lib
在运行 a.out 前,可以通过环境变量指定库的加载路径,也可以把库移到 /lib 目录下:
a. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库的绝对路径
b. sudo mv libnb.so /lib
运行:./02main
C++程序中,经常会调用C语言中的代码(C语言的各种库函数)
g++ 01main.cpp -o 01main -
I
./inc -l
nb -L./lib/tmp/ccnbSknm.o:在函数‘main’中:
01main.cpp:(.text+0x6a):对‘print_array(int*, int)’未定义的引用
collect2: error: ld returned 1 exit status事实上,C++代码是无法直接调用C语言代码的,因为g++编译器在编译的时候生成的函数名和C语言编译器生成的函数名不一样(g++编译器找不到C语言库中的函数名)
解决方法:
在声明这个函数的时候,告诉C++编译器,此函数的调用方式按照C语言的方式调用(不对函数进行改名)extern "C"
{
void print_array(int *a, int n);
//.....
}如果这样写,在C++中使用是没有问题的,但是如果在其他的C文件中使用这个头文件时,就会编译不通过
因为:extern "C",不是C语言的关键字,gcc编译器不认识,导致C语言程序又不能使用这个库了
能不能改进一下代码,gcc编译器的时候不使用这个关键字,g++编译器的时候就使用这个关键字
=======>
条件编译,利用g++编译器内置的宏 __cplusplus#ifdef __cplusplus extern "C" { #endif void print_array(int *p, int num); #ifdef __cplusplus } #endif
nb.h ---> inc
#ifndef __NB_H__ #define __NB_H__ // 类型的定义和函数的声明 #ifdef __cplusplus extern "C" { #endif void print_array(int *a, int n); #ifdef __cplusplus } #endif #endif
gcc -shared -fpic -o libnb.so ../src/nb.c -I../inc/
g++ 01main.cpp -o 01main -I../inc -lnb -L../lib
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../lib
./01main
gcc 02main.c -o 02main -I../inc -lnb -L../lib./02main
(4) 内联函数内联函数是为了提高程序的运行效率而实现的
使用关键字 inline 修饰的函数,称为内联函数
格式:
inline 返回值类型 函数名(参数列表) {
函数体;
}
c++在 struct 和 class 中定义的函数,默认就是inline
内联函数的特点:将函数定义为内联函数,编译器在编译的时候,就会将函数的函数体复制到函数的调用点,那么在函数调用的时候就不需要跳转到函数定义的地方,也不需要在函数运行完毕的时候跳转回来
这样可以节省函数的调用开销,提高程序的运行效率
内联函数的思想是使用空间换时间
注意:
inline只是对编译器的一种提示,编译器不一定会按照程序员的要求来,编译器有自己的判断规则,所以,内联函数不能太长,不能包含复杂的逻辑运算,如:递归,循环,switch ....在C++中,一般使用内联函数替代宏函数(宏函数不是函数,宏定义)
#define MIN(a, b) ((a) < (b) ? (a) : (b)) // 有BUG int a = 7, b = 8; MIN(a++, b); // 会对a++展开多次 ====> inline int min(int a, int b) { return a < b ? a : b; } ====================================================== #include <iostream> #define MIN(a, b) ((a) < (b) ? (a) : (b)) // 有BUG inline int min(int a, int b); // 声明 int main() { int a = 1, b = 3; std::cout << MIN(a++, b) << std::endl; // 2 std::cout << a << std::endl; // 3 int c = 1, d = 3; std::cout << min(c++, d) << std::endl; // 1 std::cout << c << std::endl; // 2 return 0; } inline int min(int a, int b) { // 定义 return a < b ? a : b; }
05 C++中的组合类型(结构体/共用体/枚举)
在C++中,结构体/共用体/枚举类型,成为了真正意义上独立的类型
最直接的体现:当你定义好类型名之后,就可以直接使用类型名来定义变量
如:
struct Dog // Dog就是一个类型名
{
int color;
int age;
};
C语言:
struct Dog wc; struct Dog *t;
C++:
Dog wc; Dog *t;
(1) 共用体(union)C和C++中的用法,没有区别(多个成员共用一块内存区域)
union Test { long a; int b[5]; double c; }; sizeof(Test) == 24; (64位:最大数据类型的整数倍)
(2) 枚举类型(enum)
枚举就是把一个变量可能出现的值,一个一个的列举出来,变量的值,只有可能是枚举值中的一种
如:
enum WEEK{MON, TUE, WED, THU, FRI, SAT, SUN};这句话有两个作用:
1. 声明了一个新的数据类型,类型名叫做WEEK
WEEK w; // 定义了一个WEEK类型的变量w
2. 声明 MON,TUE,WED,THU,FRI,SAT,SUN为符号常量,也叫做枚举常量,可以直接使用,默认值是0~6
在传统的枚举中,枚举量的值是暴露在最外层的作用域的,这样如果在同一个作用域下面有两个不同的枚举类型
但是包含相同的枚举值时,就会有问题,同时传统的枚举总是默认使用int类型实现enum WEEK{MON, TUE, WED, THU, FRI, SAT, SUN};
enum WORKDAY{MON, TUE, WED, THU, FRI};======>
在新标准中使用范围枚举(给枚举值加了一个作用域范围)
同时解决了传统枚举总是被隐式转换为 int 的问题(内部默认实现就是int)
范围枚举:enum class 枚举类型名{枚举常量};
如:
enum class WEEK{MON, TUE, WED, THU, FRI, SAT, SUN};枚举常量的使用方式:
枚举类型名::枚举常量
如:
WEEK::MON编译: g++ 1.cpp -std=c++11
传统的枚举总是默认使用int类型实现,在C++11中,可以指定枚举的底层实现类型语法:
enum class 枚举类型名:存储类型名{枚举常量};
如:
enum class WEEK:char{MON, TUE, WED, THU, FRI, SAT, SUN};
#include <iostream> using namespace std; // enum WEEK{MON, TUE, WED, THU, FRI, SAT, SUN}; // enum WORKDAY{MON, TUE, WED, THU, FRI}; // int main() { // WEEK w; // w = MON; // cout << w << endl; // int x = MON; // cout << x << endl; // return 0; // } // 范围枚举 enum class WEEK{MON, TUE, WED, THU, FRI, SAT, SUN}; enum class WORKDAY{MON, TUE, WED, THU, FRI}; int main() { WEEK w; w = WEEK::MON; cout << (int)w << endl; // 0 int x = (int)WORKDAY::FRI; cout << x << endl; // 4 return 0; }
(3) 结构体(struct)
C++中的结构体,最基本的用法和C语言类似,都是用来描述一个自定义的复杂的类型的但是在C语言的基础上增加了很多功能
而且弱化了"结构体"的概念,注重的是对象的概念
结构体表示的就是一个具体的类型
C中的 struct 里面不能有函数(行为)
C++中的 struct 里面可以有函数(行为)
C++是面向对象的编程思想,一切皆对象!!!
结构体就是用来描述复杂的对象的
对象应该具有:
静态的属性(数据) —— 成员变量
动态的行为(函数) —— 成员函数