C/C++语言基础--从C到C++的不同(上)
本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- 之前更新的C语言,感谢大家的点赞+收藏+关注,接下来我们逐步也开始更新C++;
- C语言后面也会继续更新知识点,如内联汇编;
- 本人现在正在写一个C语言的图书管理系统,1000多行代码,包含之前所学的所有知识点,包括链表和顺序表等数据结构,请大家耐心等待!!
文章目录
- 从C语言到C++
- 1. 头文件
- 2. 命名空间
- 定义与使用
- std
- 3. IO设备之输入输出
- 4. 基本数据类型
- bool类型
- 强弱类型
- NULL和nullptr
- const
- C语言中的冒牌货
- C++中的真货
- const字符指针
- 5. 变量的初始化
- 1,概念区分
- 2,列表初始化
- 3,直接初始化
- 6. 动态内存分配
- 7. 三目运算符
- 8. 引用
- 什么是引用?
- 创建引用
- 注意事项
- 引用的用处
- 引用的本质
- 9、注意:临时变量
- 什么是临时变量
- 临时变量的特性
- 临时变量的产生
- 10. if初始化
从C语言到C++
C++是为了解决C语言不是面向对象语言发明而来的,最先叫做“C with class”,后面随着C++的发展,“C with class”改名为C++,又由于C++兼容C语言,故后面将C和C++合并,称成“C/C++”。
从编译器看(以linux为例):在linux中,C语言的编译器的GCC,C++的是G++,但是G++可以编译C语言。
从语法看:C++文件中兼容任何C语言的语法,但是会比C更严格,如下:
// C:
int* a = malloc(sizeof(int));
// C++:
int* a = (int*)malloc(sizeof(int))
但是C++完全兼容C语言的,所以就有了学C++必先学C的说法。
1. 头文件
C++为了兼容C,支持所有的C头文件,但为了符合C++标准,所有的C头文件都有一个C++版本的,即去掉.h,并在名子前面加c。如<cstring>和<cmath>,例如:
C语言 | C++ |
---|---|
stdio.h | cstdio |
math.h | cmath |
string.h | cstring |
stdlib.h | cstdlib |
… | … |
2. 命名空间
定义与使用
案例:假设在标准库中有一个变量叫做,A,但是你也定义了一个变量A,那这样就会发生冲突,这个时候最简单的办法就是修改我们自己定义的变量A的名字,比如说改为C,但是如果我就想用A呢?
这个时候,C++大叔提出了一个叫做命名空间的概念,将不同变量可以储存在不同空间中,这样就很容易区分了。本质上就是定义一个范围,将不同变量储存到不同范围里。
- 定义方式:
namespace name //name为自定义命名空间名
{
//代码声明
}
- 使用方式:
name::code; //code可以是变量或函数...
using name::code; //只使用name下面的code
using namespace name; //使用name里面的所有内容
std
std是什么?
std是一个空间的ID,就像我们人的身份证一样,在该空间中储存了所有标准库的变量、函数、类…………,比如说输出输出:std::cin、std::cout
为什么将cout放到命名空间中?
是因为像cout这样的对象在实际操作中或许会有好几个,比如说你自己也可能会不小心定义了一个对象叫cout,那么这两个cout对象就会产生冲突,尤其是再引入多库的时候,虽然是不同的库,但是函数名字一样,这个时候编译器就会发现不识别的情况。
3. IO设备之输入输出
前言:IO运用是非常广泛的,学到后面会发现到输出IO设备(软件、硬件)
- C语言的的输入输出用的主要是scanf()、printf()函数
- 而C++是使用类对象cin、cout进行输入输出
- 如下:
int a;
double b;
char name[20];
std::cin >> a >> b >> name;
std::cout << a << b << name;
-
cin 输入流对象
-
cout 输出流对象
-
endl 换行,并清空输出缓冲区(end line 结束一行,并另起一行)
-
\n照样可以在cout中使用
-
也可以换个写法:
-
using namespace std; // 声明使用的标准库 int a; double b; char name[20]; cin >> a >> b >> name; // 可以省略std:: cout << a << b << name;
4. 基本数据类型
bool类型
C++和C语言的基本数据类型几乎一样
char short int long long float double unsigned signed ...
值得注意的是,C语言中虽然也有bool(布尔类型),但是需要包含头文件<stdbool.h>,而在C++中则不用,直接使用即可。
bool cmpare(int a,int b)
{
return a > b;
}
cout << compare(2,3) << endl;
// 输出:0
注意:在C++中,bool类型如果想输出true、false,需要加上一点东西:
std::cout << boolalpha << compare(2,3) << std::endl;
加上boolalpha这个,起到格式化作用。
强弱类型
- C语言:强类型,弱检查—— 一般就叫做弱类型了,对变量定义不严格,很多东西不检查
void* p = NULL;
int* p1 = p;
int* pn = NULL;
void* pp = pn;
//无报错,无警告,完美
在C语言中,void*可以和其他类型指针相互转换!
- C++:强类型,强检查 —— 真正意义上的强类型
void* p = NULL;
int* p1 = p; //错误 “初始化”: 无法从“void *”转换为“int *”
int* pn = NULL;
void* pp = pn; //正确 任意类型的指针都可以自动转为万能指针
在C++中,void*不能直接转换为其他类型的指针,但是可以把其他类型的指针转为void*
NULL和nullptr
NULL在C语言作用是,给指针赋值为空,但是查阅源码,我们可以发现:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
NULL本质是一个宏定义,实现机制的将0转化为void*类型数据,但是在C++中,**void***是不能转换成其他类型的,故从源码来看,他会转化为0(C++中),这明显是不对的,故在C++中引入了nullptr代表空指针.
接下来我们来看一个C++中使用NULL的例子,代码如下所示:
#include<iostream>
using namespace std;
void func(int x)
{
cout << __FUNCSIG__<< endl;
}
void func(char* px)
{
cout << __FUNCSIG__ << endl;
}
int main()
{
//都调用的整数版本的func函数
func(2); //void __cdecl func(int)
func(NULL); //void __cdecl func(int)
return 0;
}
从运行结果来看,无论是数字还是NULL都是调用的,参数为int类型的函数,这是毋庸置疑的,C++中NULL就是0,但是这个结果更本不符合语义,我们传NULL,肯定是想传一个空指针进去的,而不是作为一个整数0,使用nullptr
后
int main()
{
func(2); //void __cdecl func(int)
func(nullptr); //void __cdecl func(char *)
return 0;
}
const
C语言中的冒牌货
const作用是说明那个变量不能修改,但是C语言中的const并不是真正的常量,只是表示const修饰的变量为只读。
const int num = 18;
通过指针间接修改只读变量的值:
int* pt = (int*)#
*pt = 19;
printf("%d %d\n", num,*pt); //output:19 19
可以看到常量it的值已经通过指针被间接改变,但是C++中不行。
C++中的真货
为了兼容C语言做出了什么改变?
int* pt = (int*)#
*pt = 19;
cout << num << " " << *pt << endl; //output:18 19
- 明明已经通过指针修改了a值,为什么输出却没有变呢?
- 解释(重要):
C++编译器当碰见常量声明时,在符号表中放入常量,那么如何解释取地址呢?(编译期间即可确定)
编译过程中若发现对const使用了&操作符,则给对应的常量单独分配存储空间(为了兼容C)
const 的奇葩情况
当给C++中的常量赋值一个变量时,它又变得和C语言一样了;(在程序运行期间分配内存)
int num = 20;
const int a = num; //赋值变量
int* p = (int*)&a;
*p = 21;
cout << a << " " << *p << endl; //output:21 21
const字符指针
在C++中const修饰的字符指针,不能直接赋值给没有const修饰的指针,需要强制类型转换,或者把被赋值的指针也声明为const。
char* name = "maye"; //错误,C语言中可以
const char*name ="maye"; //正确
- 函数参数为字符指针的时候需要特别注意
void show(char* name)
{
cout << name << endl;
}
void test()
{
show("maye"); //"const char *" 类型的实参与 "char *" 类型的形参不兼容
//void show(const char* name) //请把函数原型里的参数加上const
}
5. 变量的初始化
在C++中变量的初始化,有了很多简单有趣的操作。
1,概念区分
在C++语言中,初始化与赋值并不是同一个概念:
初始化:创建变量时赋予其一个初始值。
赋值:把对象(已经创建)的当前值擦除,而用一个新值来代替。
2,列表初始化
作为C++11新标准的一部分,用花括号来初始化变量得到了全面应用(在此之前,只是在初始化数组的时候用到)。列表初始化有两种形式,如下所示:
int a = 0; //常规
int a = { 0 };
int a{ 0 };
说明:上述的两种方式都可以将变量a初始化为0。
2.1 局限
当对内置类型使用列表初始化时,若初始值存在丢失的风险,编译将报错,如:
int a = 3.14; //正确,编译器会警告 “初始化”: 从“double”转换到“int”,可能丢失数据
int a = {3.14}; //错误,编译器会报错 从“double”转换到“int”需要收缩转换
3,直接初始化
如果在新创建的变量右侧使用括号将初始值括住(不用等号),也可以达到初始化效果
int a(20);
其他实例:
const char* name("wy");
char sex[3]("男");
const char* name{ "wy" };
char sex[3]{"男"};
cout << name << " "<<sex << endl;
char id[5]{ 1,2,3,4,5 }; //正确
char id[5](1,2,3,4,5); //错误
6. 动态内存分配
在C语言中是利用库函数malloc和free来分配和释放内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。
malloc的职责仅仅是分配内存,new除了分配内存外,还干一件事,调用构造函数,但是malloc的底层调用的是malloc
free的职责仅仅是释放内存,delete除了释放内存之外,还干一件事,调用析构函数,但是delete的底层调用的是free
- 申请对象:
Type* pointer = new Type;
//...
delete pointer;
示例:
int* p = new int;
cout<<*p<<endl;
delete p;
以上代码输出-842150451
,据此可以知道,new是不会自动初始化内存的,那么我们可以在new的时候,指定初始值,简单方便!
int *p = new int(18);
- 申请对象数组:
Type* pointer = new Type[N];
//...
delete[] pointer; //数组的释放必须加上[]
示例:
int* parr = new int[5];
for (int i = 0; i < 5; i++)
{
cout << parr[i] << " ";
}
delete[] parr;
output:-842150451 -842150451 -842150451 -842150451 -842150451
输出时垃圾值,同样的,也可以在new的时候初始化,因为野指针很恐怖的!!!
int* parr = new int[5]{0};
- 定位放置
一般来说,使用new申请空间时,是从系统的“堆”(heap)中分配空间。申请所得的空间的位置时根据当时的内存的实际使用情况决定的。但是,在某些特殊情况下,可能需要在指定的特定内存创建对象,这就是所谓的**“定位放置new”**(placement new)操作。
定位放置new操作的语法形式不同于普通的new操作:
- 一般都用如下语句A* p=new A,申请空间,
- 定位放置new操作则使用如下语句A* p=new (ptr) A,申请空间,其中ptr就是程序员指定的内存首地址。
Type* pointer = new(ptr) Type;
//根据情况是否释放内存
示例:
int a = 123;
int* p = new(&a) int;
cout << a << " " << *p << endl; //123 123
通过定位放置new,把对象a所在的空间首地址,返回了回来,所以输出的值也是123。在这里不需要释放内存哦!
小结:
- new 和 malloc不要混用
- 分配内存使用完,记得释放内存(数组和普通变量释放有些微区别)
7. 三目运算符
三目运算符,又名条件运算符。可以在合适的情况下,代替if…else…语句,让代码变得更简洁。
在C语言和C++中的条件表达式的值的类型是不一样的,C语言中返回的是一个值,也就是常量;C++中返回的是变量本身,如下:
void test()
{
int a = 2;
int b = 3;
int max = (a>b?a:b); //获取ab中最大的值 C √ C++ √
(a>b?a:b) = 66; //把ab中最大的那个变量,赋值为66 C × C++ √
}
- 无论是在C语言还是C++中,条件表达式都可以作为一个值,赋值给其他变量;
- 所以,C语言中的条件表达式不能作为左值,即不能赋值,而在C++中却是可以的,因为C:返回变量值,C++:返回左值。
思考:既然说C++中返回的是变量的本身,那在C语言中如何模拟呢?
C语言还有一个可以修改变量内容的东西,可以突破作用域,那就是指针。
*(a > b ? &a : &b) = 520;
8. 引用
什么是引用?
引用就是一个变量名字的别名,当int A;
赋值给int& B = A;
的时候,A就是B,B就是A,A、B一样,语法如下:
Type &refName = variable_name;
创建引用
先来定义一个变量。
int i = 18;
再为变量i声明一个引用。
int& r = i;
在这些声明中,& 读作引用。因此,第一个声明可以读作 “r 是一个初始化为 i 的整型引用”;
cout<<i<<" "<<r<<endl;
i和r的值都为18,因为他们两个其实都是同一块内存空间的名字。
r = 20;
1cout<<i<<" "<<r<<endl;
当通过引用修改了值之后,i的值也会发生变化,都输出20。
注意事项
- 引用必须初始化
int& refa; //错误 没有初始化
int a = 8;
int& refa = a; //正确
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象(只是把b的值赋值给了refa,而不是让refa引用b),因为引用的本质就是:常量指针
int a = 8,b = 9;
int& refa = a;
refa = b; //** 只是把b的值赋值给了refa **,而不是让refa引用b
- 如果要引用右值,那么必须使用常量/右值引用 临时变量的机制
int& refc = 12; //错误 “初始化”: 无法从“int”转换为“int &”,非常量引用的初始值必须为左值
const int&refc =12; //正确
-
当然,也可以使用右值引用来引用常量;或者使用std::move()把左值转成右值
-
右值引用,尤其是在指针、指针指针上用处特别大,结合std::move
int&& refr = 21;
-
引用经过std::move()转换过的变量
int a = 123; int&& refr = 21;
常引用和右值引用有什么区别呢?
1,常引用引用的值是不可以修改的;但是右值引用引用的值是可以修改的!(大多数情况用常引用:函数参数)
2,右值引用一般实现资源权限的转移
-
引用的用处
- 作为函数参数
//在函数内部改变实参的值需要传变量的地址
void fun(int* n)
{
*n=18
}
//指针是非常危险的,因为指针所指向的内存空间,不确定,需要额外判断
fun(nullptr); //传nullptr 会发生中断
//在C++中,除了使用指针外,还可以通过引用来达到这个目的
void fun(int& n)
{
n=18
}
- 作为函数返回值
int& getAge()
{
int age = 18;
return age; //注意:不要返回局部变量的引用或地址,可以使用静态变量或全局变量替代
}
int& refAge = getAge();
refAge = 23;
引用的本质
引用如此神奇,那么引用的本质到底是什么呢?
- 引用在C++中,内部实现是一个常指针:type &name <==> type*const name
- 发明引用就是,**(二级指针)难以区分,而且也不好看,故发明引用,可以减少一个*的使用。
9、注意:临时变量
这个本来之前就该更新的,但是忘了,在我的笔记中记录在这里。
什么是临时变量
//一个例子
double a = 10.82;
int& b = (int)a; //会报错,常引用不能作为左值
/*
*原因:
*强制转换是先生成一个临时变量 10 ,然后在赋值给变量储存,如果没有变量储存就会立马被释放
*解决:右值引用,const常引用
*/
临时变量的特性
- 都要被const修饰
- 临时变量不能被非常引用修饰(常引用与右值引用的不同点:看上面(两点))
临时变量的产生
- 类型转换
- 调用函数的时候,将实参数的值传递给形参时
- 在函数返回时候,返回值也会以临时变量的形式返回
复制构造函数的时候, 请务必遵循规范, 加上const.
10. if初始化
在C++17开始,就可以直接在if中初始化了,如下:
if(int v = 10; v > 9) {
std::cout << "Hello World" << std::endl;
}