命名空间
即可由程序员自定义的作用域,域中的定义,在各作用域之间互不干扰。
作用是解决命名冲突
匿名命名空间不用给作用域起名
const
宏定义是在预处理时做字符串的直接替换,不做类型检查。
const赋予只读属性,有具体的类型,在编译时会做类型检查
指针xx与xx指针
- 常量
- 指针常量(pointer to const):
- int const * xxx const int * xxx
- 无法修改该指针指向的值,但能修改指向
- 常量指针
- int * const
- 可以修改指向的值,不能修改指向
- 指针常量(pointer to const):
- 数组
- 数组指针
-
int arr[5] = {1,2,3,4,5}; //arr代表数组首元素地址 //&arr代表数组的首地址,虽然与数组首元素地址相同,但实际上单位为整个数组,即&arr+1是整个数组后面一位的地址 int (*p)[5] = &arr;//数组指针 //(*p)[]可取出数组的元素
-
-
指针数组
- 存储指针的数组
- 数组指针
-
函数
-
函数指针
-
void func(){} int main(){ void (*func_ptr)() = &func; func_ptr();//即通过函数指针调用函数。}
-
-
指针函数
- 返回值为指针的函数。可以作用于指向全局变量(不然指向局部变量,函数结束则发生未定义行为)
-
new/delete 与malloc/free
-
new/delete是操作符,而malloc/free是库函数。
-
new返回值为对应类型的指针,malloc返回值为void*
-
new可以直接初始化,malloc申请的空间有脏数据。
-
malloc参数为字节数,new则不需要传递大小。
引用(最重点)
概念
引用是一个已定义变量的别名
语法:
int num = 2;
int & ref = num;
注意:
- &在这里不是取地址符号,而是引用符号(有个空格)
- 引用类型需与与其绑定的变量的类型相同(学习继承后则有变动)
- 声明引用的同时必须初始化,否则报错
- 引用一经绑定,无法更改绑定
本质
C++的引用实际上是一种被限制的指针
通常的说法中,引用不占据内存,只是一个变量的别名,但从原理理解,引用底层也是由指针实现的。所以也占据一个指针的大小。
对该引用取地址,获取到的就是绑定变量的地址
引用与指针的联系与区别?
联系:
- 引用与指针都是用来间接访问变量
- 引用的底层仍是指针,可以视为一个受限制的指针
区别
- 引用必须初始化,指针可以不初始化
- 引用不能修改绑定,但是指针可以修改指向;
- 在代码层面对引用本身取址取到的是绑定变量的地址,但是对指针取址取到的是指针变量的地址。
使用场景
作为函数的参数(重点)
学习引用之前,若想通过形参(函数的参数)改变实参的值,只能通过指针才能达到目的。引用则比指针更不容易犯错。<span style=color:red;background:yellow>在C++中推荐使用引用而非指针作为函数的参数。</span>
//在实参传给swap3时,如swap3(a,b)
//其实就是发生了初始化int & x = a与int & y = b;
void swap3(int & x, int & y){//引用传递,不复制
int temp = x;
x = y;
y = temp;
}
注意事项
- 不要返回局部变量的引用。因为局部变量会在函数返回后被销毁,被返回的引用就成为了”无所指”的引用,程序会进入未知状态。
int & func()
{
int number = 1;
return number;
}
- <span style=color:red;background:yellow>不要轻易</span>返回一个堆空间变量的引用,非常容易造成内存泄漏。
int & func()
{
int * pint = new int(1);
return *pint;
}
void test()
{
int a = 2, b = 4;
int c = a + func() + b;//内存泄漏,没释放呢
}
-----------------------------------------------
int & func3(){
int *p = new int(10);
return *p;
}
void test1(){
int & ref = func3();//借刀杀人,这样处理才行,此行会执行一次func3
cout<< ref <<endl;//这些不会执行
cout<< &ref <<endl;
delete &ref;
}
总结
-
使用引用时,如果只是单纯给某个变量取别名,那没什么意义。主要目的在于函数的参数传递中,解决大块数据或对象的传递效率和空间不理想的问题。
-
用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,还可以通过const的使用,保证了引用传递的安全性。
-
引用与指针的区别:
- 指针是通过某个指针变量指向一个变量后,对它所指向的变量间接操作。主要还是因为使用指针难以看懂,程序可读性差。
- 引用的底层仍是指针,但编译器不允许直接访问该底层指针。逻辑上可直接理解为对引用的操作,各方面都是对绑定变量的操作。**可以用指针或引用解决的问题,更推荐使用引用**。
强制转换
C语言风格的强转有不少缺点
TYPE a = (TYPE)EXPRESSION;
- 它可以在任意类型转换,比如把一个指向const对象的指针,转换为非const对象指针,转前转后简直不是一个物种了,这并不合适。
- 不容易查找,如(int)xx这样的形式,在c++程序中寻找如大海捞针。
所以c++为了克服这些缺点,引进了4个新的类型操作转换符(cast=铸造)
static_cast;
const_cast;
dynamic_cast;
reinterpret_cast;
static_cast
最常用,使用形式:
目标类型 转换后的变量 = static_cast<目标类型>(要转换的变量)
例子:
int inum = 100;
float fnum = 0;
fnum = static_cast<float>(inum);
void*指针转换成其他类型也可以:
void *pVoid = malloc(sizeof(int));
int *pInt = static_cast<int*>(pVoid)
但不允许两个指针类型之间转换,如
int i = 1;
int *pi = &i;
float *f = static_cast<float *>(i)
// clang: Static_cast from 'int *' to 'float *' is not allowed
好处:不允许非法的转换,且方便查找。
const_cast
修改类型的const属性,基本不用。
指向常量的指针被转化成普通指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;
const int number = 100;
int * pInt = &number;//error
int * pInt2 = const_cast<int *>(&number);
dynamic_cast
该运算符主要用于基类和派生类间的转换,尤其是向下转型的用法中(后面讲)
reinterpret_cast
功能强大,比static_cast的使用范围更广一些,慎用(也称为万能转换)
它可以用来处理无关类型之间的转换,即用在任意指针(或引用)类型之间的转换,以及指针与足够大的整数类型之间的转换。
由此可以看出,reinterpret_cast的效果很强大,但错误的使用reinterpret_cast很容易导致程序的不安全,**将转换后的类型值转换回到其原始类型,才是reinterpret_cast最正确的使用方式**。
函数重载
在c++中允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数重载。
**在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。**实现函数重载的条件
函数参数的数量、类型、顺序任一不同则可以构成重载。
只有返回类型不同,参数完全相同,是不能构成重载的
函数重载的实现原理
实际上是通过名字改编实现,即给函数加入参数类型后缀,如add(int a,int b)变为addii
extern “C”
如果在C++代码中想要对部分内容按照C的方式编译
extern "C" void func() //用 extern"C"修饰单个函数
{
}
//如果是多个函数都希望用C的方式编译
//或是需要使用C语言的库文件
//都可以放到如下{}中
extern "C"
{
//……
}
默认参数
C++可以给参数定义默认值,如果将func1函数参数中的x定义成默认值0, y定义成默认值0
void func(int x = 0, int y = 0){
cout << "x = " << x << endl;
cout << "y = " << y << endl;
}
void test0(){
func(24,30);
func(100);
func();
}
<span style=color:red;background:yellow>通常是将默认值的设置放在声明中而不是定义</span>
默认参数应从右至左逐渐定义。当调用函数时从左向右匹配参数
bool
任何非零值都将转换为true,而零值转换为false(<span style=color:red;background:yellow>注意:-1也是代表true</span>)
inline函数
**—— 在普通函数定义之前加上inline关键字**(1)inline是一个建议,并不是强制性的
(2)inline的建议如果有效,就会在<span style=color:red;background:yellow>编译时</span>展开,可以理解为是一种更高级的代码替换机制
(3)函数体内容如果太长或者有循环之类的结构,不建议inline,以免造成代码膨胀;比较短小并且比较常用的代码适合用inline。
比如函数体中有循环结构,那么执行函数体的开销比调用函数的开销大得多,设为内联函数只能减少函数调用的开销,没有太大意义。
<span style=color:red;background:yellow>对比总结:</span>
宏函数
优点:只是进行字符串的替换,并没有函数的开销,对于比较短小的代码适合使用;
缺点:没有类型检查,存在安全隐患,而且比较容易写错。
普通函数
优点:可调试,有类型检查,比宏函数更安全;
缺点:函数的调用会增加开销。
内联函数
既具备宏代码的效率,又增加了安全性,**所以在C++中应尽可能的用内联函数取代宏函数。**
内联函数注意事项
-
内联函数采用声明和实现分离的写法
inline int func(int a, int b); int func(...){...;}
-
**如果要把inline函数声明在头文件中,则必须把函数定义也写在头文件中。**
若头文件中只有声明没有实现,被认为是没有定义替换规则。
如下,foo函数不能成为内联函数:
**inline函数在头文件必须有定义。**inline void foo(int x, int y);//该语句在头文件中 void foo(int x, int y)//实现在.cpp文件中 { //... }
-
谨慎使用内联
以下情况不宜使用内联:
-
如果函数体内的代码比较长,使用内联将导致可执行代码膨胀过大。
-
如果函数体内出现循环或其他复杂的控制结构,那么执行函数体内代码的时间将比函数调用开销大得多,因此内联的意义并不大。
-
异常处理
C++ 异常处理涉及到三个关键字:try、catch、throw.
注意:**catch的是类型,不是具体信息**
if(xxx){
throw "xxx";
}
if(yyy){
throw 1;
}
try {
//语句块
} catch(const char *) {
//具体的异常处理...
} ...
catch(double x) {//类型
//具体的异常处理...
}
内存布局(重要)
64位系统,理论空间达到16EB(2^64),但是受硬件限制,并不会达到这么多;
以32位系统为例,一个进程在执行时,能够访问的空间是虚拟地址空间。理论上为2^32,即4G,有1G左右的空间是内核态,剩下的3G左右的空间是用户态。从高地址到低地址可以分为五个区域:
-
栈区:操作系统控制,由高地址向低地址生长,编译器做了优化,显示地址时栈区和其他区域保持一致的方向。(同一个区域,先定义的内容在较低的地址)
-
堆区:程序员分配,由低地址向高地址生长,堆区与栈区没有明确的界限。
-
全局/静态区:读写段(数据段),存放全局变量、静态变量。
-
文字常量区:只读段,存放程序中直接使用的文字常量和全局的常量,如const char * p = “hello”; hello这个内容就存在文字常量区。
-
程序代码区:只读段,存放函数体的二进制代码。
C风格字符串
C风格字符串即以空字符结尾的字符数组。
-
如果用数组形式保存字符串,注意留出终止符,当然也可以用上语法糖来初始化字符数组
void test0(){ char str1[] = {'h','e','\0'}; char str2[] = "world" }
-
如果用指针形式,直接定义为const char * ,C++代码中标准C风格字符串的写法。
const char * pstr = "hello"; pstr[0] = 'h'//error,不给改
-
<span style=color:red;background:yellow>注意:</span>输出流运算符具有默认重载效果,cout«char*时会进行自动的访问,输出的是字符串内容,而不是地址。