六、C++的布尔类型(bool)
1.bool类名是C++中的基本数据类型,专门负责表示逻辑值:真/假
注:C语言的C99标准里加入了布尔类型
2.bool类型内存占一个字节,1表示true,0表示false
3.bool类型的变量可以接收任意类型表达式的结果,其值非0则为true,0则为false
bool func()
{
return true/false;
}
4.插入流控制符`boolalpha`:以字符串的形式打印bool值(就是打印true/false不打印数字)
cout << "b=" << boolalpha << b << endl;
参考代码:
#include <iostream>
using namespace std;
int main(void)
{
bool b = false;
cout << "size=" << sizeof(b) << endl;//1
cout << "b=" << b << endl;//0
b = 3+5;
cout << "b=" << b << endl;//1
b = 1.2 * 3.4;
cout << "b=" << b << endl;//1
char* p = NULL;//NULL-->(void*)0
b = p;
cout << "b=" << b << endl;//0
return 0;
}
七、操作符别名
&& <==> and
|| <==> or
! <==> not
{ <==> <%
} <==> %>
......
参考代码:
#include <iostream>
using namespace std;
int main(void)
<%
int a=3;
int b=0;
if(a or b)<%
cout << "true" << endl;
%>
else<%
cout << "false" << endl;
%>
return 0;
%>
★八、C++函数
1.函数重载
1)定义:在相同作用域下,定义同名的函数,但是他们的参数必须有所区分,这样的多个函数构成重载关系
//C语言方式实现
void drawRect(int x,int y,int w,int h){}
void drawCircle(int x,int y,int r){}
......
//C++方式实现
void draw(int x,int y,int w,int h){}
void draw(int x,int y,int r){}
......
函数重载特征:
①相同的作用域(在同一个类中)
②函数名相同
③参数不同(与返回值类型,参数常属性,volatile和默认参数无关),或者其常函数:当编译器决定哪个函数被声明,定义或调用时,每个参数的const和volatile类型和修饰和默认参数会被忽略掉。
特例:
void f(int * x)
void f(const int *x)或void f(int const *x) // 编译正常
//但是,第二个不能为void f(int * const x)
void f(int * x)
void f(volatile int *x)或void f(int volatile *x) // 编译正常
//但是,第二个不能为void f(int * volatile x)
④virtual关键字可有可无没有影响
参考代码:
#include <iostream>
using namespace std;
int func(int i){
cout << "func(int)" << endl;
}
void func(int i,int j){
cout << "func(int,int)" << endl;
}
void func(int a,float b){
cout << "func(int,float)" << endl;
}
int main(void)
{
func(100);
func(100,200);
func(100,1.23f);
//通过函数指针调用重载关系的函数时,由
//函数指针的类型决定其匹配的重载版本
void (*pf)(int,float) = func;
pf(100,200);
return 0;
}
2)函数重载匹配
调用重载关系函数时,编译器将根据实参和形参的匹配程度,自动选择最优的版本
当前g++编译器的匹配规则:
完全匹配 >= 常量转换 > 升级转换 > 降级转换 > 可变参数(...)
参考代码:
#include <iostream>
using namespace std;
//char->int:升级转换
void foo(int i){
cout << "foo(1)" << endl;
}
//char->const char:常量转换
void foo(const char c){
cout << "foo(2)" << endl;
}
//short->char:降级转换
void fun(char c){
cout << "fun(1)" << endl;
}
//short->int:升级转换
void fun(int i){
cout << "fun(2)" << endl;
}
//省略号匹配(最差)
void bar(int i,...){
cout << "bar(1)" << endl;
}
//double->int:降级转换
void bar(int i,int j){
cout << "bar(2)" << endl;
}
int main(void)
{
char c = 'A';
foo(c);
short s = 10;
fun(s);
bar(10,1.23);
}
3)函数重载的原理:
C++编译器通过对函数进行换名,将参数表的类型信息整合到新的名字中,解决函数重载和名字冲突的矛盾
问:extern "C"的作用?
答:在C++函数声明时加入extern "C",可以强制要求C++编译器不对该函数进行换名,方便C程序调用该函数
注:extern "C"声明的函数不能重载
extern "C" void func(void)
{
......;
}
2.哑元参数
1)定义:定义函数时,只有类型而没有变量名的形参称为哑元
void func(int)//哑元参数
{
......;
}
2)应用场景:
①操作符重载时区分前后++/--
②兼容旧代码
//算法库
void video_math(int a,int b){...;}
//使用者
int main()
{
video_math(10,20);
......;
video_math(30,40);
......;
}
_____________________________________
//算法库省级
void video_math(int a,int)//第二个参数为哑元参数
{
video_math(10,20); //不影响旧代码的函数调用
......;
video_math(30,40);
......;
}
3.函数缺省参数(默认参数)
1)可以为函数的部分参数或全部参数指定缺省值,调用该函数时,如果不给实参,就取缺省值作为相应的值
2)靠右原则,如果一个参数有缺省值,那么该参数右侧的所有形参都必须带有缺省值
3)如果函数的定义和声明分开,缺省参数应该写在函数的声明部分,而定义部分不写
#include <iostream>
using namespace std;
void func(int a,int b=200,int c=300); //函数的声明
int main()
{
func(111);//111,200,300
func(111,222); //111,222,300
func(111,222,333); //111,222,333
}
void func(int a,int b/*=200*/,int c/*=300*/) //函数的定义
{
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
}
4.内联函数(inline)
1)定义:使用inline关键字修饰函数即为内联函数,编译器会尝试对内联进行优化,避免函数的调用开销,提高程序的执行效率
2)使用说明:
①多次调用小而简单的函数适合内联
②调用次数极少或者大而复杂的函数不适合内联
③递归函数不能内联
④虚函数不能内联
注:内联函数只是一种建议而不是强制要求,一个函数能否内联优化取决于编译器,有些函数不加inline关键字也会默认处理为内联优化,有些函数即便加了inline修饰也会被编译忽略
九、动态内存分配
1.C++使用操作符实现动态内存的分配
分配:new/new[]
释放:delete/delete[]
错误处理:异常机制
#include <iostream>
using namespace std;
int main(void)
{
//int* pi = new int;
//*pi = 100;
//分配内存同时初始化
int* pi = new int(100);
cout << *pi << endl;
delete pi;//防止内存泄露
pi = NULL;//放置野指针
int* pi2 = new int(100);
(*pi2)++;
cout << *pi2 << endl;//101
delete pi2;
pi2=NULL;
/*int* parr = new int[10];
for(int i=0;i<10;i++){
parr[i] = i;
cout << parr[i] << ' ';
}*/
//new数组同时初始化,C++11
int* parr =
new int[10]{1,2,3,4,5,6,7,8,9,10};
for(int i=0;i<10;i++){
cout << parr[i] << ' ';
}
cout << endl;
delete[] parr;
parr=NULL;
return 0;
}
2.C++中的new/delete和C中malloc/free的区别和联系
1)两者都是在堆上进行的内存操作
2)new与delete只能在C++中使用,但malloc和free可以在C++和C中使用,new与delete是操作符,可以重载,malloc和free是库函数
3)使用new分配内存的时候,会自动相应类的构造函数进行初始化,而malloc只是单纯的分配内存,不进行初始化,delete释放内存时会调用相应类的析构函数,free单纯的释放内存
4)Malloc函数的返回值是void*类型的,new的返回值某种类型指针,无需再进行类型转换
5)Malloc分配内存时,分配内存的大小需要自己计算,但new是自动计算的
3.delete和delete[ ]的区别
1)当new[]中的数组元素是基本类型时,通过delete和delete[]都可以释放数组空间;
2)当new[]中的数组元素是自定义类型时,只能通过delete[]释放数组空间。当数组中的元素是自定义类型时,delete在释放空间时只会调用数组中首个元素的析构函数,而delete[]在释放空间时会调用数组中所有元素的析构函数
★十、C++的引用
1.定义:
1)引用即别名,引用就是某个变量的别名,对引用的操作与对该变量本身操作完全相同
2)语法:
类型 & 引用名 = 变量名;
注:引用在定义时必须初始化,初始化以后就不能修改目标
注:引用的类型与绑定的目标变量类型一致
参考代码:
int a = 10;
int& b = a;
b++;
cout << a << endl;//11
int c = 20;
b = c; //不是让b引用c,而是将c赋值给b
cout << a << endl; //20
2.常引用
1)定义引用时加const修饰,即为常引用,不能通过常引用修改引用的目标的值
const 类型 & 引用名 = 变量;
类型 const & 引用名 = 变量;
//两种效果一样
int a = 10;
const int& b = a;//b是a的常引用
b++;//error
a++;//ok
cout << b << endl;
2)普通的引用也可以叫做左值引用,只能引用左值,而常引用也叫万能引用,既可以引用左值,也可以引用右值
3)关于左值和右值
左值(lvalue):可以放在赋值操作符左边,可以被修改
常见左值情况:
①普通变量
②前++/-- ——表达式结果是左值(++a)
③赋值表达式结果是左值(=/+=/-=......)
右值(rvalue):智能放在赋值操作符右边,不能被修改
常见右值情况:
①常量,字面值常量
②多数算数表达式结果都是右值(+/-/*/......)
③后++/-- ——表达式结果是右值(a++)
④临时变量都是右值(隐式转换)
⑤一般函数返回的临时变量也是右值(引用型返回值是个左值)
#include <iostream>
using namespace std;
int func(void){
int num = 10;
cout << "&num:" << &num << endl;
return num;//int tmp = num;
}
int main(void)
{
//int r = tmp
//r实际引用的函数返回的临时变量(tmp)
const int& r = func();
cout << r << endl;//10
cout << "&r=" << &r << endl;
int a = 10;
//将a隐式转换为char类型,结果保存临时变量
//rc实际要引用的是临时变量
//char& rc = a;//error
const char& rc = a;//ok
cout << "&a=" << (void*)&a << endl;
cout << "&rc=" << (void*)&rc << endl;
return 0;
}
3.引用型函数参数
1)将引用用于函数的参数,这时形参就是实参的别名,可以通过形参直接修改实参,同时可以避免参数传递的过程,减小函数的调用开销,提高代码的执行效率
2)引用型参数有可能意外修改实参的值,如果不希望修改实参本身,可以将形参声明为常引用,可提高传参效率的同时还可以接收常量型的实参
参考代码:
#include <iostream>
using namespace std;
void swap1(int* x,int* y){
*x = *x ^ *y;
*y = *x ^ *y;
*x = *x ^ *y;
}
void swap2(int& x,int& y ){
x = x ^ y;
y = x ^ y;
x = x ^ y;
}
int main(void)
{
int a=3,b=5;
//swap1(&a,&b);
swap2(a,b);
cout << "a=" << a << ",b=" << b << endl;
}
4.引用型函数返回值
1)可以将函数的返回类型声明为引用,避免函数返回值带来的内存开销,提高代码执行效率
2)如果函数返回左值引用,那么该函数调用结果就也是一个左值
注:不要从函数中返回局部变量的引用,因为所引用的内存在函数返回以后将被释放,使用非常危险,但是可以从函数中返回成员变量、静态变量、全局变量引用
参考代码:
#include <iostream>
using namespace std;
struct A{
int data;
int& func(void){
return data;
}
int& func2(void){
int i=100;
//返回局部变量引用,危险!
return i;
}
};
int main(void)
{
A a = {0};
//data = 123;
a.func() = 123;
cout << a.data << endl;//123
return 0;
}
5.引用和指针
1)从C语言的角度看待引用,引用本质就是指针,但是在C++中推荐使用引用而不是指针
int i = 100;
int& ri = i;
int* const pi = &i;
ri <=等价=> pi
2)指针定义时可以不做初始化,其指向目标可以初始化后再修改(指针常量除外),而引用定义时必须初始化,且一旦初始化后,其引用目标不能在改变
int a,b;
int* p;//ok
p = &a;//p指向a
p = &b;//p指向b
------------------
int& r;//error
int& r = a;//r引用a,r只能是a的别名
r = b;//ok,但不是修改引用目标,仅是赋值操作
了解:
3)可以定义指针的指针(二级指针),但是不能定义引用的指针(指向引用的指针)
int a;
int* p = &a;
int** pp = &p;//ok,pp称为二级指针
------------------------------
int& r = a;
int&* pr = &r;//error
int* pr = &r;//ok,但是就是一个普通指针
4)可以定义指针的引用(指针变量的别名),但是不能定义引用的引用
int a;
int* p = &a;
int*& rp = p;//ok,指针的引用
----------------------------
int& r = a;
int&& rr = r;//error,C++中叫右值引用
int& rr = r;//ok,但是就是一个普通的引用
5)可以定义指针数组,但是不能定义引用数组
int a,b,c;
int* parr[3] = {&a,&b,&c};//ok
int& rarr[3] = {a,b,c};//error
6)可以数组引用(数组的别名)
int arr[3] = {1,2,3};
int (&rarr)[3] = arr;//ok
7)和函数指针类似,也可以定义函数的引用(函数的别名),其语法特性和函数指针一样
void func(int a,int b){...}
int main(void){
void (*pf)(int,int) = func;
pf(100,200);//ok
---------------------
void (&rf)(int,int) = func;
rf(100,200);//ok
}
7.C++的引用和 C 语言的指针有什么区别
①值:指针指向一块内存,它的内容是指向内存的地址;引用是某变量的别名
②初始化:引用在定义的时候必须初始化,且不能为空,指针定义时可以为空
③改变:引用在定义是被初始化一次,之后不可变;指针可变
④存储:程序为指针变量分配内存区域,而引用不需要分配内存区域
⑤使用:引用使用是无需解引用,指针需解引用