创作人QQ:851301776,邮箱:lfr890207@163.com,欢迎大家一起技术交流,本博客主要是自己学习的心得体会,只为每天进步一点点!
个人座右铭:
1.没有横空出世,只要厚积一定发。
2.你可以学历不高,你可以不上学,但你不能不学习
一、C++的布尔类型(bool)
1 bool类型是C++中的基本数据类型,专门表示逻辑值:逻辑真为true,逻辑假为false
2 bool类型内存占一个字节:1表示true,0表示false
3 bool类型的变量可以接收任何表达式结果,其值非零则为真,真为零则为false.
#include <iostream>
using namespace std;
int main(void)
{
bool b = false;
cout << "b=" << b << endl;//0
cout << "size=" << sizeof(b) << endl;//1
b = 4*6;
cout << "b=" << b << endl;//1
b = 1.2 + 3.4;
cout << "b=" << b << endl;//1
int* p = NULL;//(void*)0
b = p;
cout << "b=" << b << endl;//0
return 0;
}
二、操作符别名
&& <==> and
|| <==> or
! <==> not
!= <==> not_eq
{ <==> <%
} <==> %>
#include <iostream>
using namespace std;
int main(void)
<%
int a = 0;
int b = 1;
if(a and b)<%
cout << "true" << endl;
%>
else<%
cout << "false" << endl;
%>
return 0;
%>
这些在实际开发中很少见
三、C++的函数
1.函数重载(overload)
(1)定义
在相同作用域中,定义同名的函数,但是它们的参数必须有所区分,这样的函数将构成重载关系。
注:函数重载和函数返回类型无关
eg:实现一些绘图函数
//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){}
#include <iostream>
using namespace std;
void func(int i)
{
cout << "func(int)" << endl;
}
void func(int i,int j)
{
cout << "func(int,int)" << endl;
}
//int func(int a,int b){}
void func(int i,float f)
{
cout << "func(int,float)" << endl;
}
int main(void)
{
func(10);
func(10,20);
func(10,1.23f);
//函数指针的类型决定其匹配的重载版本
void (*pf)(int,float) = func;
pf(10,20);//func(int,float)
return 0;
}
(2)函数的重载匹配
调用重载关系的函数时,编译器将根据实参和形参的匹配程度,自动选择最优的版本,当前g++编译器匹配的一般规则: 完全匹配>=常量转换>升级转换>降级转换>省略号匹配
#include <iostream>
using namespace std;
//char-->int:升级转换
void bar(int i)
{
cout << "bar(1)" << endl;
}
//char-->const char:常量转换
void bar(const char c)
{
cout << "bar(2)" << endl;
}
//short->char:降级转换
void hum(char c)
{
cout << "hum(1)" << endl;
}
//short->int:升级转换
void hum(int i)
{
cout << "hum(2)" << endl;
}
//省略号匹配
void foo(int i,...)
{
cout << "foo(1)" << endl;
}
//double->int:降级转换
void foo(int i,int j)
{
cout << "foo(2)" << endl;
}
int main(void)
{
char c = 'a';
bar(c);//bar(2)
short s = 200;
hum(s);//hum(2)
foo(10,1.23);//foo(2)
}
(3)函数重载的原理
C++编译器是通过对函数进行换名,将参数表的类型信息整合到新的函数名中,解决函数重载和名字冲突的矛盾。
extern "C" 声明的作用:可以在函数声明前面加入 extern "C",要求C++编译器不对该函数进行换名,方便C程序直接调用该函数。
被extern "C“声明的主要作用:
- extern是全局的意思,这些函数其他地方都可以访问
- "C"的作用是:按照C的方式编译函数(函数名不更换)
原因如下:
注:被extern "C"声明的函数不能重载
不加extern "C"修饰的函数,编译(g++ -c hello.c);
nm:查看目标文件的标识符(nm hello.o)
源文件的hello函数名被替换为了:_Z5hellov
- _Z:是编译器内置的,没什么含义
- 5:指的是函数名占字符长度
- hello:原来的函数名称
- v:表示void(参数的类型为void)
加extern "C"修饰的函数,编译(g++ -c hello.c);
2.函数的缺省参数(默认实参)
1)可以为函数的参数指定缺省值,调用该函数时,如果不给实参,就取缺省值作为默认实参.
返回类型 函数名(参数类型 变量名=缺省值,...)
void func(int a = 0/*缺省参数*/){}
2)靠右原则,如果函数的一个参数有缺省值,那么该函数右侧的所有参数都必须带有缺省值
void func(int a = 0,int b);//error
void func(int a,int b = 0);//ok
3)如果函数的定义和声明分开,缺省参数应该写在函数的声明部分,而定义部分不写。
void func(..);//函数声明
void func(..){...};//函数定义
#include <iostream>
using namespace std;
//函数声明
void func(int a,int b=20,int c=30);
//注意不要形成歧义的错误
//void func(int i){}
int main(void)
{
func(11,22,33);//11 22 33
func(11,22);//11 22 30
func(11);//11 20 30
}
//函数定义
void func(int a,int b/*=20*/,int c/*=30*/)
{
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "c=" << c << endl;
}
3.哑元参数
1)只有类型而没有变量名的形参称为哑元
void func(int/*哑元*/){}
2)使用场景
- 操作符重载中
- 为了兼容旧代码
eg:
算法库:
void math_func(int a,int b){...}
使用者:
int main(void)
{
math_func(10,20);
..
math_func(10,20);
..
}
升级算法库:
void math_func(int a,int/*哑元*/){...}
使用者:
int main(void){
math_func(10,20);
..
math_func(10,20);
..
}
4.内联函数(inline)
1)使用inline关键字修饰的函数,即为内联函数;编译器会尝试对内联函数进行优化,避免函数调用开销,提高代码执行效率。
//内联函数
inline void func(...){...}
2)使用说明
- 多次调用小而简单的函数适合内联
- 调用次数很好或者大而复杂函数不适合内联
- 递归函数不能内联
- 虚函数不能内联
注:内联只是一种建议而不是强制要求,一个函数能否内联优化主要取决于编译器,有些函数不加inline修饰也会默认处理为内联,有些函数即便加了inline修改也会被编译器忽略。
四、C++的动态内存管理
1C中动态内存管理
- 分配:malloc()
- 释放:free()
- 错误处理:返回值
2 C++中使用new和delete操作符实现动态内存管理
- 分配:new、new[]
- 释放:delete、delete[]
- 错误处理:异常
eg:分配一块内存,保存一个整型数
int *pi = (int*)malloc(4);//C中
*pi = 123;
free(pi);
-------------------------------
int *pi = new int;//C++中
*pi = 123;
delete pi;
#include <iostream>
using namespace std;
int main(void)
{
int* pi = new int;
*pi = 123;
cout << *pi << endl;
delete pi;//避免内存泄露
pi = NULL;//避免野指针
//分配内存同时初始化
int* p2 = new int(321);
cout << *p2 << endl;//321
(*p2)++;
cout << *p2 << endl;//322
delete p2;
p2 = NULL;
//new数组
int* parr = new int[10];
for(int i=0;i<10;i++){
parr[i] = i+1;
cout << parr[i] << ' ';
}
cout << endl;
delete[] parr;
parr = NULL;
//new数组同时初始化,C++11
int* parr2 =
new int[10]{10,9,8,7,6,5,4,3,2,1};
for(int i=0;i<10;i++){
cout << parr2[i] << ' ';
}
cout << endl;
delete[] parr2;
parr2 = NULL;
return 0;
}
3.区别
- malloc和free为库函数,new和delete为关键字
- malloc强调的所占字节数,而new强调的类型的概念
- malloc只能分配,不能初始化,但是new可以完成分配和初始化,类似于calloc
- 在对象实例化的过程中,new首先分配内存,然后调用构造函数,完成对象初始化,而malloc仅仅可以完成内存分配
五、C++的引用(reference)
1 定义
1)引用即别名,就是某个变量的别名,对引用的操作符和对变量本身操作完全相同
2)语法
类型 & 引用名 = 变量;
注:引用在定义时必须初始化,初始化以后所绑定的变量不能再修改。
注:引用的类型和初始化的变量类型要一致.
int a = 123;
int & b = a;//b就是a的别名
#include <iostream>
using namespace std;
int main(void)
{
int a = 10;
int& b = a;//b引用a,b就是a的别名
b++;
cout << "a=" << a << ",b=" << b << endl;
cout << "&a="<< &a<< ",&b="<< &b<< endl;
//int& r;//error,定义必须初始化
int c = 20;
b = c;//只是赋值操作,不是修改引用的目标
//a=20,b=20
cout << "a=" << a << ",b=" << b << endl;
cout << "&a="<< &a<< ",&b="<< &b<< endl;
//char& rc = c;//error,类型要一致
return 0;
}
2 常引用
1)定义引用时加const修饰,即为常引用
const 类型 & 引用名 = 变量;
类型 const & 引用名 = 变量;//和上面等价
注:不能通过常引用修改目标变量。
int a = 123;
int& b = a;
b = 321;//ok
-------------
int a = 123;
const int& b = a;
b = 321;//error
2)普通引用也叫左值引用,只能引用左值;而常引用也叫万能引用,既可以引用左值也可以引用右值。
#include <iostream>
using namespace std;
int main(void)
{
int a = 10;
a = 20;
//10 = 20;
int& r1 = a;//ok
//int& r2 = 10;//error
const int& r2 = 10;//ok
int b = 20;
//(a+b) = 30;//error
cout << "(a=b):" << (a=b) << endl;//20
cout << "a=" << a << endl;//20
(a = b) = 30;
cout << "a=" << a << endl;//30
a = 10;
cout << ++a << endl;//11
cout << a << endl;//11
++a = 20;//ok
cout << a << endl;//20
++++++a;
cout << a << endl;//23
a = 10;
cout << a++ << endl;//10
cout << a << endl;//11
a++ = 20;//error
}
关于左值和右值
左值(lvalue):可以放在赋值操作符左侧,可以被修改
- 普通变量都是左值
- 赋值表达式结果是左值
- 前++、--表达式结果是左值
右值(rvalue):只能放在赋值操作符右侧,不能被修改
- 常量都是右值
- 大多数算数表达式结果是右值
- 后++、--表达式结果是右值
- 函数返回的临时变量是右值
- 类型转换的结果是右值
#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;
//C语言的写法(交换x和y的值)
void swap1(int* x,int* y){
*x = *x ^ *y;
*y = *x ^ *y;
*x = *x ^ *y;
}
//C++的写法(交换x和y的值)利用引用
void swap2(int& x,int& y){
x = x ^ y;
y = x ^ y;
x = x ^ y;
}
int main(void)
{
int a = 10,b = 20;
//swap1(&a,&b);
swap2(a,b);
cout << "a=" << a << ",b=" << b << endl;
return 0;
}
#include <iostream>
using namespace std;
struct Teacher{
char name[100];
int age;
};
void print(const Teacher& t)
{
cout << t.name << ','<< t.age/*++*/ << endl;
}
int main(void)
{
const Teacher t = {"xxx",45};
print(t);
print(t);
return 0;
}
4 引用型函数返回值
1)可以将函数返回类型声明为引用,这时函数返回结果就是return后面数据的别名,可以避免函数返回值所带来的开销,提高代码执行效率
2)如果函数返回普通引用(左值引用),那么该函数返回结果就也是一个左值。
注:不要从函数中返回局部变量的引用,因为所引用的内存会在函数返回以后被释放,使用危险!但可以返回成员变量、静态变量、全局变量的引用.
int& func(void){
static int num = 100;
return num;
}
func() = 200;//ok
#include <iostream>
using namespace std;
struct A{
int data;
int& func(void){
return data;
}
int& foo(void){
int num = 100;
return num;//危险!!
}
};
int main(void)
{
A a;
//a.data = 123;
a.func() = 123;
cout << a.data << endl;//123
int& rnum = a.foo();
cout << rnum << endl;//段错误
return 0;
}
5 引用和指针
1)如果从C的角度看引用的本质,就是指针,但是在C++开发中,推荐使用引用而不是指针。
int i = 123;
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;//仅是赋值操作,r还是a的别名
3)可以定义指针的指针(二级指针),但是不能定义引用的指针.
int a;
int* p = &a;
int** pp = &p;//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
int& rr = r;//ok,但rr就是普通引用
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 i,double d){}
void (*pf)(int,double) = func;//函数指针
pf(10,1.23);
--------------------------------
void (&rf)(int,double) = func;
rf(10,1.23);