函数重载指的是在同一个作用域中,声明了具有相同函数名的函数,它们的参数列表不同,也就是说参数类型不同,参数个数不同,参数顺序不同,返回值同不同都可以。
/*函数重载*/
int add(int x, int y)
{
cout << "int add(int x, int y)" << endl;
return x + y;
}
double add(double x, double y)
{
cout << "double add(double x, double y)" << endl;
return x + y;
}
int main()
{
int ret1 = add(1, 2); //3
double ret2 = add(5.5, 4.3);//9.8
cout << ret1 << " " << ret2 << endl;
return 0;
}
因为在链接阶段生成符号表的时候,C语言对函数取名的时候,只会拿函数名进行取名,如果有函数重载的话,没办法区分函数的不同。而C++在取名的时候,是将函数名和参数类型的首字符结合起来对函数的取名,这样就可以区分函数的不同了。
在作用域中:函数重载在需要在同一个作用域中。函数重定义和函数重写的两个函数必须一个在父类中,一个在子类中,而且函数重写必须是虚函数。
函数重载和函数重定义需要的是函数名相同,参数列表不同,函数重写需要函数名相同、参数列表相同和返回值相同(例外情况是协变和析构函数的重写。协变是返回值可以不同,但是返回值必须是父子关系类的指针或引用。析构函数的重写是函数名不相同)。
引用就是给一个变量取别名,跟被引用的变量共用一块地址空间。比如
int a = 10;
int& ra = a;
在定义的时候必须进行初始化,而且初始化后不能改变引用对象。
①引用在定义时必须初始化,而指针不需要。②引用不能初始化为空引用,而指针可以初始化为空指针。③引用初始化后不能改变指向对象,而指针可以改变指向。④引用没有开辟内存,是与被引用的变量共用内存地址,而指针是开辟了新的空间,用于存放被指向的变量的地址⑤在使用sizeof的时候,引用的结果是引用类型大小,指针的结果是地址空间所占的字节个数。比如有一个double类型的变量b,其引用为rb和指针为pb,那么在sizeof后,得出的结果是8和4.其中,8是double类型的大小,而4是32位平台下,指针的大小。
double b = 6.6;
double& rb = b;
double* pb = &b;
cout << sizeof(rb) << endl;//8
cout << sizeof(pb) << endl;//4
⑥引用在自加的时候,是引用的那个实体加一,而指针自加是向后偏移一个类型大小。比如当rb+1,那么是b从6.6变成7.6,而pb+1,是地址往后偏移了4位。
cout << rb + 1 << endl;//7.6
cout << pb<<"->" << pb + 1 << endl;//0055FA6C->0055FA74--每次编译运行都会变
引用一般使用在参数和返回值上面。可以通过引用减少拷贝,提高程序的效率。而作为返回值的话,引用的对象必须不能随函数的销毁而销毁。
static用于修饰局部变量、全局变量和函数。
被修饰的局部变量变成静态局部变量,其作用域不变,但是改变了生命周期,会跟随程序的结束而销毁。
被修饰的全局变量会变成静态全局变量,其作用域改变,不再具有外部链接属性,其它源文件不能使用extern来声明外部符号从而引用这个变量。
被修饰的函数会变成静态函数,其作用域改变,不再具有外部链接属性,其它源文件不能通过extern来声明从而引用这个函数。
在类中,成员变量被修饰后,是属于所有类的,所有类的对象都可以调用它,而且是不需要this指针去引用。
被inline修饰的函数会变成内联函数,在编译的时候,编译器会将内联函数进行展开,不好有函数栈帧的开销。在短小而且频繁调用非递归的函数可以使用内联函数。
宏的优点是增强代码的复用性,比如用宏来定义一个常量,那么在后续的代码中我或许需要多次用到这个常量。还有就是可以提高性能。比如需要实习一些简单的加减功能的函数,可以使用宏来定义,就避免了函数的栈帧开销,提高性能。
缺点是不方便调式,因为在编译期间进行了替换。代码的可读性比较差,宏展开后代码可能会变得复杂,冗余。没有类型安全的检查,在宏中不会对参数类型进行检查。
#define add(x,y) x+y
int main()
{
int a = 1, b = 2;
cout << add(a, b) << endl;
return 0;
}
解决办法是使用内联函数和常量定义,比如const、enum来替换。
const除了上述,可以使用const来替换宏定义,因为const常量定义是可以进行类型检查、并且具有作用域。
const还能用来修饰局部变量,修饰常量字符串,修饰指针和修饰函数的参数和返回值。
const修饰指针,分有常量指针和指针常量。
常量指针说的是指针的指向是一个常量,不可以通过指针去修改这个常量的值,但是指针可以改变指向。
指针常量说得是它是一个常量,它不可以改变指向,但是可以通过指针修改指向的内容。
const int* a;//常量指针,指针a指向的内容不可以通过指针去改变指向的内容,但是可以改变指向
int const* a;//常量指针
int* const a;//指针常量,可以通过指向a去修改内容,但是不可以改变指向
const char* a = "abc";//字符串常量,不可以修改内容和指向
sizeof关键字是一个运算符,用来计算数据类型大小,会包含'\0'
strlen是一个函数,计算的是字符串有效字符的长度,不包含'\0'
比如
const char* str1 = "abcde";//sizeof==4,指针的大小是4/8 strlen=5/字符串实际长度
char str2[] = "abcd";//sizeof==5,不是指针,因此算的是字符串的内存大小,加上\0 strlen=4 字符串实际长度
char str3[5] = { 'a' };//sizeof==5 char类型数组的内存大小 strlen计算来的是随机值,因为没'\0'
int str4[5] = { 'a' };//sizeof==20 int类型数组的内存大小 strlen计算来的是随机值,因为没'\0'
char str5[] = { "012345678" };//sizeof==10 strlen==9
volatile是一种类型修饰符,作用是当一个变量被声明为volatile时,编译器会禁止将该变量的读取和写入操作放入寄存器中,确保每次读取和写入都直接访问内存。使用场景一般是在多线程的共享变量上。
const和volatile结合使用,可以让该变量具有常量,且不会被编译器优化。当其它一些线程对该变量进行修改时,可以告诉编译器,这个变量不要优化,可以去内存中读取最新的值。
指针被volatile修饰
告诉编译器该指针所指向的对象可能会在不同的时间被外部因素更改,并且在访问该指针时应该直接读取或写入内存,而不做任何优化。