# 1. 变量的类型
1.1 变量的定义位置
C变量的定义必须在作用域的开始位置,而C++可以在任何位置。
1.2 命名空间
1.2.1 命名空间概述
1.2.1.1 C语言中
- C中同个文件可以定义多个同名的全局变量,只要不超过一个初始化就不会报错,多个同名全局变量也是同一个内存地址,编译器默认未初始化的为变量的声明;
- C中只有一个全局作用域,所有的全局变量共享一个全局作用域,因此初始化的全局变量之间不能同名;
1.2.1.3 C++中
- C++提出了命名空间的概念,将全局作用域分成不同的部分,不同命名空间的变量可以同名而不会冲突,命名空间可以相互嵌套;;
- C++的全局作用域为默认的命名空间;,因此即使未初始化,也不允许同个文件定义多个同名全局变量;
1.2.2 使用方法
- 通过
namespace 空间名称 { 定义变量}
创建命名空间; - 通过
using namespace 空间名称
使用命名空间,using name :: variable
使用命名空间中的变量,::variable
使用默认命名空间的变量;
int a = 10; //定义全局变量
namespace first //定义命名空间first
{
int i = 10;
}
namespace second //定义命名空间second
{
int i = 20;
namespace third //嵌套定义命名空间third
{
int i = 30; //third命名空间中定义变量
typedef struct str {
int i;
}str;
}
}
int main()
{
using namespace first; //使用first中的所以变量
printf("i = %d\n", i); //输出为10, 因为使用的时first中的i
using namespace second;
printf("i = %d\n", i); //报错,无法确定使用first还是second的i
using namespace second::third;
str p = {40};
printf("i = %d\n", i); //报错,无法确定使用first还是third的i
printf("a = %d\n", ::a); //输出为10
printf("i = %d\n", second::i); //输出为20,使用的时second中的i
printf("i = %d\n", second::third::i); //输出为30
printf("i = %d\n", p.i); //输出为40
}
1.3 默认类型
func()
与int func(viod)
的区别:C++不支持默认类型- C中
func()
对参数个数没有限制,参数和返回值的类型默认都为int
型,func(void)
则无法传参,返回值也必须定义; - C++中
func()
与func(void)
的含义相同,即都无法传参,参数列表为空;
func1() //C编译器不会报错,C++编译器会报错,要求必须定义返回值类型
{
return 10;
}
func2(void) //在C++中与func2()含义相同
{
return 5;
}
int main(int args, char * argv[])
{
func1(1, 2, 3, 4); //C编译器不会报错, C++编译器会报错
func2();
}
1.4 布尔类型
- C语言中无
bool
类型 ,而是通过0与非0来代替; - C++中的布尔类型
(1)C++中bool
类型是一个普通的基本数据类型,在内存中占用1个Byte,取值范围为true
(数值为1来表示)和false(数值0来表示),可以用来定义数组、全局变量等;
(2)bool类型可以进行正常的算数运算,但由于只有1和0两个值,因此除了0之外的值都算作1,本质与C相同;
int main(void)
{
bool b = 0;
printf("%d\n", b); //输出为0;
b++;
printf("%d\n", b); //输出为1;
b = 93;
printf("%c\n", b); //输出依然为1
printf("%d\n", b); //输出ascll码表中顺序为1的符号
}
1.5 字符串
1.5.1 基本概述
C语言中不存在真正的字符串类型,而是通过字符数组的方式来实现的。
- C++中为了兼容C语言,因此也不存在字符串类型
- 由于C++可以自定义类,因此C++当中可以通过定义类的方式,来实现同字符串类型完全相同属性的类型;
- 通过C++中标准库中提供的字符串类就可以实现字符串排序、比较大小、字符串查找和提取等操作;
注意:string是类,因此其后定义的是string类的对象,而非string类型的变量,虽然使用上相同,但本质不同。
void string_sort(string str[], int len) //比字符串比较函数
{
for (int i = 0; i < len; i ++)
{
for (int j = i; j < len; j ++)
{
if (str[i] < str[j]) //直接比较字符串的大小
{
swap(str[i], str[j]);
}
}
}
}
string string_add(string str[], int len) //字符串相加函数
{
string ret;
for(int i = 0; i < len; i ++)
{
ret += str[i] + ";"; //直接进行字符串之间的相加
}
return ret;
}
int main()
{
string str[5] = {"abc", "bcd", "cde", "def", "efg"};
string_sort(str, 5);
for (int i = 0; i < 5; i ++)
{
cout << str[i] << endl;
}
cout << string_add(str, 5) << endl;
}
1.5.2字符串与数字相互转换
- C++中的字符串类中提供了字符串流类,包括输入流与输出流,用来字符串与数字之间的相互转化。
- 需要包含字符串流类头文件
<sstream>
。
#include <sstream> //包含字符串流类文件
int main()
{
istringstream s1("123.45"); //字符串输入流
double num;
s1 >> num; //字符串转为数字,返回值是bool,判断是否右移成功
cout << num << endl;
ostringstream s2; //字符串输出流
s2 << 543.21 << 432.34; //数字转换为字符串, 返回值是字符,表明可以连续的转换
string str = s2.str();
cout << str << endl;
}
1.6 三目运算符
- C中三目运算返回的是变量的数值是常量,而C++中默认返回的是变量本身;
- 若三目运算符两个右值中有一个是常量,则C++返回的值也是常量类型;
int func()
{
int a = 1;
int b = 2;
(a > b ? a : b) = 3 //C编译器会报错, C++不会报错
printf("b = %d\n", b); //C++输出b = 3;
(a > b ? a : 2) = 3; //C++会报错
}
1.7 新型的类型转换
1.7.1 C的强制类型转换
- 由于C语言中对强制类型转换无限制,对于任何类型都可以进行强制类型转换,太过于粗暴;
- 在大型的C程序中,无法定位出强制类型转换的地方;
- 以下程序强制类型转换将会报警告,且结果无法预计;
int main()
{
struct ps{ //定义结构体
int i;
char *name;
};
typedef int (pf)(int); //定义函数类型
int a = 0x12345678;
char c = 'c';
pf *f = (pf *)a; //将int型a转为pf型函数赋给函数f
c = (char) a; //将int型a转为char型字符赋给字符c
ps *s = (ps *)a; //将int型a转为ps型结构体赋给结构体指针s
}
1.7.2 C++类型转换
static_cast
:用于基本类型之间、用于由继承关系类类对象之间和类指针之间;不能用于普通指针之间;
int func()
{
int i = 0x12345678;
char c = 'c';
int *pi = &i; //定义int*指针
char *pc = &c; //定义char*指针
c = static_cast<char>(i); //成功,将int型i转为char型赋给c
*pc = static_cast<char *>(pi); //失败,static_cast不支持指针类型之间准换
}
const_cast
:消除变量的只读属性,仅用于const
修饰的指针之间和引用之间;
int func()
{
const int &x = 1; //内存分配了一块内存,里面存放着1这个数值,由于1时常量,因此其引用x前必须加const表示这个内存中是常量无法修改;
int &y = const_cast<int &>(x); //取消了只读属性只读属性,表示这块内存可以修改
y = 5;
printf("x = %d\ny = %d\n", x, y); //输出为x = 5, y = 5;
const int i = 1; //使用了const修饰,将i和1放到了符号区,并在内存中分配了一块只读的空间但并未使用
int &j = const_cast<int &>(i); //将内存中的空间取消了只读属性,并使j成为了该i内存的应用,符号区的i并未改变
j = 5;
printf("i = %d\nj = %d\n", i, j); //输出为i = 1, j = 5;
int a = 1;
int b = const_cast<int>(a); //报错,因为const_cast不能用于基本类型之间的转换
reinterpret_cast
:用于指针类型之间的转换;用于指针与整型之间的转换
int main()
{
int i = 0x12345678;
char c = 'c';
float f = 3.14;
int *pi = &i;
char *pc = &c;
pc = reinterpret_cast<char *>(pi); //成功
pi = reinterpret_cast<int *>(i); //成功
pi = reinterpret_cast<int *>(f); //报错,不支持浮点型转换
c = reinterpret_cast<char>(i); //报错,不能用于基本类型之间的转换
}
dynamic_cast
:用于有继承关系的类指针之间的转换、有交叉关系的类指针之间的转换、具有类型检查的功能、需要虚函数的支持;
2. 关键字
2.1 register
关键字
register
关键字的作用是希望编译器尽可能将该变量存储在寄存器中。- C中无法访问
register
修饰的变量地址,因为访问地址只是相对于内存而言,寄存器不在可访问地址范围内; - C++中可以访问
register
修饰变量的地址,因为C++编译器编译时,会将register
关键字去掉,现在编译器会自动优化决定是否放在寄存器中;
/*
* C中register起作用,无法访问其修饰变量的地址
* C++编译器会自动优化掉register,可以访问该变量地址
* /
int func(void)
{
register int a = 10;
int *p = &a; //在C编译器会报错,C++编译器不会报错
printf("%p\n", p); //C++编译器胡将a的地址打印出来
}
2.2 struct
关键字
- Struct关键字用来定义结构体类型
- C中定义结构体变量时,需要在前面加
struct
,若要去掉struct
需用typedef
重新定义类型名; - C++定义的结构体变量可以去掉
struct
关键字,直接用该类型名;
struct str {
char *name;
int name;
};
int func(void)
{
struct str a1; //C编译器和C++编译器都支持
str a2; //C编译器会报错,C++编译器不报错
}
2.3 const
关键字
2.3.1 在C语言中
const
关键字在C中,修饰的局部变量具有只读属性而并非成为常量,只是在编译器层面而非内存层面上的只读,局部变量可以通过解指针的方法修改成功;- 通过解指针的方法修改全局变量则会出现段错误,因为const修饰的全局变量存储在只读数据段;
2.3.2 在C++中
2.3.2.1 修饰普通变量
const
修饰的变量在编译时会将该变量放到符号区成为常量,同时在内存中生成一块特定的区域,此时通过解指针访问的实际上是特定的内存区域,而并未真正的访问符号区const
修饰的变量;- 当程序运行时需要该参数时,编译器会到符号区调用该参数,从而保证了该参数是常量;
- 当
const
关键字修饰的变量的初始值是另一个变量时,被修饰的变量为只读变量而不是常量; - 当
const
修饰的变量初始值为别的类型变量时,则生成一个新的变量,新变量与原来变量无关。 const
与volatile
同时修饰一个变量时,该变量是只读变量而不是常量
const int i = 10; //C中为常量
int main()
{
/*****判断a为常量****/
const int a = 10; //C中为只读变量,C++中为常量
int *pa = const_cast<int *>(&a);
*pa = 5;
printf("a = %d\n", a); //C中输出5,C++中输出为10
printf("*p = %d\n", *pa); //C和C++都输出为5
/*****判断d是只读变量而非常量******/
int b = 5;
const int d = b; //通过变量来初始化一个const修饰的变量,得到d时只读变量而非常量
int *pd = const_cast<int *>(&d);
*pd = 5;
printf("a = %d\n", d); //C中输出5,C++中输出为10
printf("*p = %d\n", *pd);
/*******判断e是新生成的只读变量*******/
char c = 'a';
const int e = c;
int *pe = const_cast<int *>(&e);
c = 'v';
printf("e = %c\n", e);
printf("c = %c\n", c);
/*******判断f为只读变量*******/
volatile const int f = 10;
int *pf = const_cast<int *>(&f);
*pf = 5;
printf("f = %d\n", f);
printf("*p = %d\n", *pf); //输出为5
}
2.3.2.2 修饰引用
const
引用:int b = 10; const int &b = a;
,即可以通过const引用来修改已定义变量的只读属性;- 当
const
引用的是一个常量时const int &b = 1
,编译器会为这个常量分配一个内存空间,内存空间的别称是这个引用,此时若无const
则会报错误,因为引用的是常量,需要const
修饰; - 当
const
引用的是一个常量时,引用与常量的关系类似于C中字符串与指向字符串的指针之间的关系;
2.4 inline
关键字
- C++的内联函数作用与C的相同,都是将函数原地展开,即节省了调用函数的开销,提高了效率,又可以通过编译器检查类型等错误;
- 内联函数的不同环境
(1)现在编译器即使无inline
关键字,也会可能自动优化为内联函数,因此对于想要内联的函数必须不能复杂;
(2)inline
只是一种请求非强制要求,编译器可能会拒绝;对于不同的环境的编译器,一般含有自己独特的inline
关键字;
2.5 new
和delete
关键字
-
C语言中通过
malloc
库函数进行动态内存分配,申请堆的内存空间,通过free
释放;C++中通过new
关键字来申请堆的空间,通过delete释放; -
C语言中
malloc
是库函数,而非C语言自带关键字,因此在某些简易环境中可能没有malloc
函数,无法申请堆内存;C++中new
是自带关键字,不受运行环境影响; -
malloc
函数申请的内存大小是以字节为单位;而new
关键字是以数据类型为单位; -
melloc
只是申请了一块空的内存空间;new
申请的空间里存放有对象,new
关键字申请单个变量空间时,可以对该空间的变量进行初始化。free
只是释放空间,而不会销毁对象,容易造成内存泄漏;delete
可以调用析构函数销毁对象并释放空间。
注意:初始化时无法进行隐式类型转换
int main()
{
int *pi = new int(1); //申请堆内存并初始化
// int *pc = new char('c'); //会报错,初始化时无法进行隐式类型转换
char *pc = new char('c');
float *pf = new float(3.14f);
int *pa = new int[10];
printf("*pi = %d\n", *pi); //输出 pi = 1
printf("*pc = %c\n", *pc); //输出 pc = c
printf("*pf = %f\n", *pf); //输出 pf = 3.140000
delete pi; //释放空间
delete pc;
delete pf;
delete []pa;
}
2.6 extern
关键字
2.6.1 C++标准库
2.6.1.1 概述
- C++中本身并不包含任何库文件,所有的库文件都是由编译器提供;
- 尽管C++标准库中涵盖了C库,但各编译器厂家又都额外增加了C语言兼容库。
2.6.1.2 C++标准库的使用
- C++标准库是由类库和函数库组成,标准库中的类和对象都位于std命名空间中;
.h
后缀是C库的头文件,C++标准库中无需带.h
后缀,其涵盖了C库的功能;
#include <iostream> //包含C++标准库头文件
#include <cmath>
using namespace std; //使用命名空间
int main()
{
double a = 3;
double b = 4;
double c = sqrt(a * a + b * b);
cout << "c = " << c << endl; //使用C++标准库函数
}
2.6.1 extern
关键字
- C++编译器可以兼容C编译器的编译方式,但会默认使用C++编译方式,因此当同一个文件中既有C和C++时,可以通过
extern“C”{}
关键字来强制{}内的内容用C的编译方式编译;
文件:add.c
int add(int a, int b)
{
return a+b;
}
文件:add.h
int add(int a, int b);
编译:gcc -c add.c -o add.o
文件:add.cpp
#include "add.h"
int main()
{
printf("%d\n", add(1, 2));
}
编译:g++ add.cpp add.h -o add
./add //报错误,无法找到add.h,因为add.h包含的add函数是通过C代码,而C++默认用C++编译方式
修改:extern关键字
extern "C"
{
#include "add.h" //C++编译器会用C的方式编译
}
int main()
{
printf("%d\n", add(1, 2));
}
编译:g++ add.cpp add.h -o add
./add //此时可以正常执行
- 条件编译判断编译环境
可以通过条件编译的方法,判断_cplusplus
C++内嵌的宏来自动确定编译环境,让C代码自动根据编译环境进行编译
#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
#include "ctest.h"
#ifdef __cplusplus
}
#endif
int main()
{
int c = add(1, 2);
printf("%d\n",c);
return 0;
}
3. 变量的引用
3.1 引用本质
- C++中的引用可以看作是一个变量的别名,与原变量名具有相同的作用,
- 语法:
类型名 &引用名 = 变量名
int func()
{
int a = 5;
int &b = a;
b = 10;
printf("a = %d\n", a); //a和b的输出结果相同
printf("b = %d\n", b);
printf("&a = %p\n", &a); //a和b的地址值相同
printf("&b = %p\n", &b);
}
3.2 引用的原理
- 引用作为变量的别名,原理上是通过指针来实现的,引用本身占用4Byte的内存空间,
int &b = a;
<==>int *const b = &a;
; - 由于引用直接指向的是变量的内存空间,因此可以像指针一样通过形参直接访问实参地址上的值。
void func1(int &a, int &b) //引用形参不需要初始化
{
int c;
c = a; a = b; b = c;
}
void func2(int *a, int *b)
{
int c;
c = *a; *a = *b; *b = c;
}
int main()
{
int a = 1, b = 2;
int c = 1, d = 2;
func1(a, b); //正确调换a和b的位置
func2(&c, &d); //正确调换a和b的位置
printf("a = %d\tb = %d\n", a, b);
printf("c = %d\td = %d\n", c, d);
}
- 引用也可以作为返回值
int& func1()
{
int a = 0;
printf("%d\n", a);
return a;
}
int& func2()
{
static int b = 1;
printf("%d\n", b);
return b;
}
int main()
{
int &n1 = func1(); //输出0
int &n2 = func2(); //输出1
printf("n1 = %d\n", n1); //输出为乱码,因为n1是局部变量a的引用,调用结束后,a的地址就内释放了,因此n1指向的原a的地址实际为野指针
printf("n2 = %d\n", n2); //输出为1, 因为n2是静态局部变量b的引用,生命周期是整个程序
n1 = 5; //因为此时n1指向的是一个栈中未知地址,因此此次赋值无意义
n2 = 10;
func1(); //输出为0
func2(); //输出为10
printf("n1 = %d\n", n1); //依然是乱码
printf("n2 = %d\n", n2); //输出为10
}
- 引用数组是错误的,因为数组需要在内存中连续分配的空间,而引用是变量的别称,引用的变量地址取决于作用域,因此不会连续分配,因此引用数组不合法。
int a = 5;
struct str {
int &x;
int &y;
int &z;
}
int func()
{
int b = 10;
int *p = new int(1);
str s1 = {a, b, *p};
int &arr[] = {a, b, *p} //报错,因为数组中的元素在内存中是连续分布的,而a,b, *p分别在静态数据区、栈和堆中,所以不能连续分布,所以报错
delete p;
}