目录
前面我们已经学习过Linux基本命令、Linux系统/网络编程和Linux常用库(json和libevent),以及shell脚本,本节开始学习C++。
说明:在学习C++之前最好有C语言基础。
C++和C语言有很多类似的内容,但是也有区别之处,并且C++在C语言基础上添加了很多东西,有了前面C语言的基础学习C++就很容易理解了。
入门级问题
为什么学习C++语言?
大型应用软件开发中,程序员往往关注的是业务逻辑的实现,很少把精力放在一些细节的实现上(比如链表);求职中,C和C++难以分割,比如C/C++工程师,C的笔试题中也会有C++;
C++在哪方面经常被应用?
主要应用于嵌入式的图像界面(QT)、游戏和后台服务器,另外也能做一些桌面应用。
第一个程序
C++的编译器和C语言不一样,C++用的是g++
注:在C++中的对象可以理解为结构体变量,即对象==结构体变量,所以对象其实本质上也是变量。
命名空间
为什么要引入命名空间?
主要是为了解决命名重复的问题,比如小李和小韩都参与了一个文件管理系统的开发,它们都定义了一个全局变量 fp,用来指明当前打开的文件,将他们的代码整合在一起编译时,很明显编译器会提示 fp 重复定义(Redefinition)错误。
为了解决合作开发时的命名冲突问题,C++ 引入了命名空间(Namespace)的概念。
1、命名空间将全局作用域分成不同的部分;
2、不同命名空间中的标识符可以同名而不会发生冲突;
3、命名空间可以相互嵌套;
4、全局作用域也叫默认命名空间。
命令空间其实就是作用域
这个是我们自己定义的作用域和作用域里面的变量
我们前面写的这行代码:using namespace std; 是官方定义了一个叫std的作用域,cout和endl都属于这个作用域里面的东西,所以我们一般都需要声明一下这个作用域,要不然编译器会报错说cout和endl错误。
那刚刚我们定义了两个作用域,我们后面要使用我们定义的变量a的时候要指定一下作用域
调用两个作用域里的函数也是一样的
或者我们也可以这样
但是 :: 这个作用域限定符的优先级比using namespace高。
注意:一般不要把using namespace这个作用域声明放在全局的位置,因为这样不安全。
所以我们是要用cout和endl的话一般都是这样写:
这样虽然麻烦但是比较安全。
因为如果在全局的位置指定了这个作用域的话,很多可能之后的一些变量都会优先选择这个std里面的,就可能产生冲突
之后我们学习的过程中的测试代码还是把它放在全局里面指定作用域,但是在大型的项目里面不建议这样使用。
C++头文件和std标准命名空间
C++ 是在C语言的基础上开发的,早期的 C++ 还不完善,不支持命名空间,没有自己的编译器,而是将 C++ 代码翻译成C代码,再通过C编译器完成编译。和C语言一样,C++ 头文件仍然以.h为后缀。后来 C++ 引入了命名空间的概念,计划重新编写库,将类、函数、宏等都统一纳入一个命名空间,这个命名空间的名字就是std。新版 C++为了避免头文件重名, 对头文件的命名做了调整,去掉了后缀.h,C语言的stdio.h变成了cstdio,stdlib.h变成了cstdlib。
C和C++语法区别
for循环,实用性加强
C++允许以下的定义语句(允许在for循环里面定义变量):
for (int i = 0; i < 5; i++);
C99以前的C语言不支持,新版本的C语言支持;
C++语言一直支持。
寄存器变量register
像这种需要频繁重复计算的变量一般都放在寄存器里面,叫寄存器变量(但是要注意寄存器的空间是非常有限的)
register int a = 1;
&a;
C语言不允许对寄存器变量做取地址操作,因为变量保存在寄存器中,而& 取地址取的是内存的地址。
C++允许对寄存器变量做取地址操作,取地址的时候,register对变量的修饰将不起作用,即变成一个普通变量。
变量检测加强
int g_a;
int g_a;
C语言中可以重复定义多个全局变量(变量名相同),这些同名的全局变量最终会被链接到全局数据区的同一块地址上(但是局部变量不能重复定义)
C++不允许重复定义全局变量。
结构体含义不同
struct Test
{
int a;
};
Test t; //在C语言中不允许这样定义,在C++中可以这样写
C语言中结构体是一组数据的集合,而不是一个类型,所以定义变量的时候需要加上关键字struct的修饰;
C++中结构体是一种类型,因此定义变量的时候可以丢掉关键字struct。
空的结构体计算长度不同
还有一个区别就是:在C语言里面这样计算出来的结构体的长度为0
但是在C++里面计算出来的不同,改成g++编译器运行的结果是1
变量/函数类型
func()
{
//.....
}
C语言允许函数不写返回值,默认返回int类型;函数括号内不写参数表示能接受任意类型和个数的参数;
C++要求函数必须写上返回值(C++中普通函数必须有返回值类型);函数括号内(形参括号内)不写参数表示不接受参数。
测试结果:
换成g++
bool变量
C++在C语言的基本类型系统之上增加了bool
C++中的bool可取的值只有true和false
理论上bool只占用一个字节,
如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现
true代表真值,编译器内部用1来表示
false代表非真值,编译器内部用0来表示
bool类型只有true(非0)和false(0)两个值
C++编译器会在赋值时将非0值转换为true,0值转换为false
我们今后写代码的时候如果只是想要函数返回是或否的结果,就可以考虑用bool类型
条件运算符
(a > b) ? a : b;
对于条件表达式,C语言返回变量的值, C++语言是返回变量本身;
C语言不能作为左值使用,C++中可以出现在程序的任何地方。
注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用,比如(a < b ? 1 : b )= 30,不能作为左值。
因为在C语言中(a>b)? a:b=100;这行代码返回的b是2,然后2=100;这时不成立的!
只读变量和常量
const
C语言中 const修饰的变量是一个只读变量,本质还是变量,有自己的地址空间;
C++中 const 变量声明的是一个真正的常量,不是变量,所以编译器不会为该常量分配空间,const 修饰的常量会被放到 符号表 中;
const int a = 1; &a; //这种操作在C++中是合法的
如果对 const 常量取地址,这一步操作会让编译器分配空间并填入常量的值,但是分配的空间并不属于该常量。可以理解成新的变量只有地址没有名字。
在C语言中只读变量可以通过地址来修改
但是在C++里面,const修饰常量类似宏定义(但是和正在的宏定义的作用域是不一样的),存放在符号表(编译结束就没了)中,不是放在内存中。
C++的const常量和宏定义define
const & define
const和define相同之处:
C++中的const修饰的,是一个真正的常量,而不是C语言中的只读变量。const修饰的常量在编译期间就已经确定下来了。
const和define不同之处:
作用域不一样。
引用&
访问变量的两种方式:变量名、地址
C++给我们提供了第三种方式:
引用的概念reference
引用可以看作一个已定义变量的别名
引用的语法:Type& name = var;
比如:
int a = 1;
int &b = a; //b可以看做是a的别名,操作b就是在操作a
普通引用在声明时必须用其它的变量进行初始化。
可以看到两处的&符号的意思是不同的,一个是引用,一个表示的是取地址,如何区分?
这就好比C语言中的*号一样,在定义的时候表示后面的变量是指针,在使用的时候表示取值。同理,&在定义的时候用就是表示引用,在使用的时候(也就是它的前面没有类型的时候)表示取地址。
一个变量可以有很多别名:
注意不能这样写:
所以引用在定义的时候必须初始化!
引用的意义
1、引用作为函数的参数,可以代替指针,引用相对于指针来说具有更好的可读性和实用性;
void swap(int &a, int &b); //引用实现两个数字的交换
2、可以用来传递复杂的数据类型,和指针一样,提高函数的调用效率。
void show(struct Test &t); //传递引用可以减小内存的开销
在这个例子中,x就是a,y就是b,交换x 和y就是在交换a 和b,x和a是同一个东西,因此是同一块内存。因此使用引用也可以实现用指针操作的效果。
而如果把int &x的&符号去掉的话,那int x形参和实参a就在不同的内存空间的,存在需要用指针完成。
引用的本质
1、引用的大小?
第一种是普通的被初始化的引用,比如int &k = a; sizeof(k);
第二种是未被初始化的引用,比如
struct Test
{
int &a;
char &b;
};
引用的大小取决于引用的对象的大小,这是正常情况下。
但是在结构体里面的引用不能初始化,如果要计算它的大小,就得考虑引用的本质
引用的本质:引用的本质就是指针常量(因为引用一旦初始化,也就是它一旦成为了一个变量的别名,它就只能是这个变量的这块内存,它不能再是别的变量的别名或者内存空间)。
注意不能这样写:
因为引用一定要引用一块内存,上面这一句中1是数字,不在内存,所以这样写是不可以的。
指针引用
void init(char *&s)
{
s = malloc(128); //此处操作指针s等价于操作main函数中的str
}
int main()
{
char *str;
init(str);
strcpy(str, “hello”);
}
引用作为返回值
int &func(); //返回值是引用
1、跟返回局部变量的地址一样,不能返回局部变量的引用;
2、可以返回全局变量的引用;
3、使用引用来接函数的返回值。
直接上代码理解:
可以返回一个全局变量
注意以下这种两种情况:
常引用
1、不能使用常量初始化普通引用,即 int &a = 1 不合法;
2、可以使用普通变量初始化普通引用,即 int &a = b 合法;
3、可以使用常量初始化常引用,即 const int &a = 1 合法;
使用常量初始化常引用的时候,操作系统会分配一块空间并填入该常量,这块空间被常引用引用。
4、可以使用变量初始化常引用,即 const int &a = b 合法。
不能通过引用a来修改b内存的值。
const int &a 相当于 const int *const a
常引用是只读的,不允许修改
函数
默认参数
default parameter
C++可以在函数声明(或者定义)时为参数提供一个默认值,当函数调用时没有指定参数值的时候,编译器会自动用默认值代替;
比如:int add(int a, int b = 1);
默认参数的规则:
一旦在一个函数调用中开始使用默认参数值,那么这个参数后的所有参数都必须使用默认参数。
后面都得有默认参数
也就是说从有默认参数值的形参开始,其后面的形参都得有默认参数值!
占位参数
占位参数只有参数类型声明,而没有参数名声明;
一般情况下,在函数体内部无法使用占位参数(因为该参数没有名字)。
int func(int a, int b, int);
int func(int a, int b, int = 0); //占位参数和默认参数的结合
占位参数有什么作用呢?
它的作用可以理解为只是占个位置而已,后面我们学了重载自增运算符的时候为了区分前置++和后置++的重载函数的时候会用到。
函数重载
Overload
含义:用同一个函数名定义不同的函数;
当函数名和不同的参数搭配时函数的含义不同 ;
函数重载的判断标准(至少满足下面一个条件):
参数个数不同
参数类型不同
参数顺序不同(比如double add(int x,double y) 和double add(double x,int y))
函数返回值不是函数重载的判断标准
比如我们一般都是这样操作:
我们观察到这两个函数的函数体基本是一样的,只是类型不一样,在C语言中必须操作没错,但是在C++中是可以函数重载的,也就是说可以把这两个函数的名字写成一样的也不会报错
这样函数名相同的情况下,到底调用哪个函数是由C++的编译器决定的,它会根据形参的类型来确定调用哪个函数,这就是函数重载。
函数重载匹配规则
将所有同名函数作为候选者,尝试寻找可行的候选函数:
1、精确匹配实参(参数的个数和参数的类型一致的)
2、通过默认参数能够匹配实参(默认参数和实参类型一致)
3、通过默认类型转换匹配实参
比如我们没有int add这个函数编译器也不会报错,就是因为有默认类型转换,当1和2实在匹配不到一个函数了,编译器就将1和2转换成double类型,使用double add这个函数
匹配失败:
1、最终寻找到的可行候选函数不唯一,则出现二义性,编译失败。
2、无法匹配所有候选者,函数未定义,编译失败。
函数重载和函数指针
int add(int a, int b);
int add(int a, int b, int c = 0);
int (*p)(int, int);
p = add; //显然p指向第一个add函数
下节开始学习类和对象!
如有问题可评论区或者私信留言,如果想要进扣扣交流群请私信!