Review cpp day02

Posted 达少Rising

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Review cpp day02相关的知识,希望对你有一定的参考价值。

回顾:
Review cpp day01

八、C++的函数

1、函数重载

  • 1)定义:在相同作用域,定义同名的函数,但是它们的参数要有所区分,这样的多个函数构成重载关系。
    注: 函数的重载和返回值无关

eg:图形库(包含很多绘图函数)

//C语言实现
	void drawCircle(int x, int y, double r){......}
	void drawRect(int x, int y, double w, double h){.....}
	.......
//C++语言实现
	void draw(int x, int y, double r){......}
	void draw(int x, int y, double w, double h){.....}
	.......

	strcpy()
	strncpy()

overload.cpp

#include <iostream>
using namespace std;

int foo(int a){
	cout << "foo(int)" << endl;
	return 0;
}

void foo(int a, int b){
	cout << "foo(int, int)" << endl;
}

void foo(int a, float b){
	cout << "foo(int, float)" << endl;
}

int main(void){
	foo(10);
	foo(10, 20);
	foo(10, 3.14f);
	//通过函数指针调用重载关系的函数,实际调用哪个版本由指针类型决定,
	//而不是由实参类型决定
	void (*pfoo)(int, float) = foo;
	pfoo(10,20);//foo(int, float)
	return 0;
}
  • 2)函数重载匹配
    • 调用重载关系的函数时,编译器将根据实参与形参的匹配程度,自动选择最有的版本。
    • 当前g++编译器的一般匹配规则:完全匹配>=常量转换>升级转换>降级转换>省略号匹配

overload.cpp

#include <iostream>
using namespace std;

//char-->int:升级转换
void bar(int i){
	cout << "bar(1)" << end;
}

//char-->const char:常量转换
void bar(const char c){
	cout << "bar(2)" << end;
}

//short-->char:降级转换
void fun(char c){
	cout << "fun(1)" << endl;
}

//short-->int:升级转换
void fun(int i){
	cout << "fun(2)" << endl;
}

//省略号匹配
void hum(int i, ...){
	cout << "hum(1)" << endl;
}

//double-->int:降级转换
void hum(int i, int j){
	cout << "hum(2)" << endl;
}
int main(void){
	char c = 'A';
	bar(c);//bar(2)
	short s = 10;
	fun(s);//fun(2)
	hum(10, 3.14);//hum(2)
	return 0;
}
  • 3)函数重载实现原理
    • C++编译器是通过对函数进行换名,将参数表信息整合到新的名字中,实现解决函数重载和名字冲突的问题。

    • 笔试题:C++中extern“C”的作用?
      答:在C++函数声明前面加入extern “C”,要求C++编译器不对该函数进行换名,方便C程序直接调用。当然这样就不能实现函数重载。

//cpp.cpp//C++文件
#include <iostream>
using namespace std;

//告诉C++编译器不要做换名操作
extern "C" void hello(void){
	cout << "hello c++" << endl;
}

//c.c//C文件
void hello(void);
int main(void){
	hello();
	return 0;
}

2、函数的哑元参数

  • 1)定义:只用类型而没有形参变量名的参数称为哑元。
    void func(int /哑元/){ }

  • 2)使用哑元的特殊场景:

    • 兼容旧代码
    • 操作符重载中区分前后++/–(后面讲到)
      eg:
	//算法库:void video_func(int a, int b){.....}
	//使用者:
	int main(void){
		vedio_func(10, 20);
	}
	//--------------------------------------
	//升级了算法库:void video_func(int a, int){......}
	//使用者:
	int main(void){
		vedio_func(10, 20);
	}

3、函数缺省参数(也称为默认实参)

  • 1)可以在声明函数时,为它的部分或全部参数指定缺省值,在调用该函数时,如果不给传递实参,就取缺省值作为相应形参的值。
    eg:
void func(int a, int b = 0){......}
func(100, 0);
func(100);

defArg.cpp

#include <iostream>
using namespace std;

//函数声明
void foo(int, int, int);
void foo(int a, int b = 20, int c  = 30){//缺省值必须靠右
	cout << a << "," << b << "," << c << endl;
}
//注意歧义错误
//void foo(int x){}//如果这个函数存在,编译foo(1)时会发生错误

int main(void){
	foo(1, 2, 3);//1,2,3
	foo(1, 2);//1,2
	foo(1);//1
	return 0;
}
  • 2)缺省参数必须靠右,如果函数的一个参数带有缺省值,那么该函数的右侧参数都必须带有缺省值。

eg:

void func(int a =0, int b){}//error
void func(int b, int a = 0){}//ok
  • 3)如果函数的定义和声明分开,缺省参数应该写在函数的声明部分,而定义部分不写。
    eg:
void func(//缺省参数);//声明
void func(){}//定义

笔试题:inline关键字作用?(见下)

4、内联函数(inline)

  • 1)定义:使用inline关键字修饰的函数,表示这个函数时内联函数,编译器将会尝试做内联优化,减小函数调用的开销.(节省函数多次调用外部函数跳转时间,典型的以空间换取时间的做法)

  • 2)场景

    • 多次调用小而简单的函数适合内联
    • 调用次数极小或者大而复杂的函数不适合内联
    • 递归函数不能内联

注: 内联只是一种建议而不是强制要求,一个函数能否内联优化主要取决于编译器,有些函数不加inline修饰也会被编译器默认处理为内联优化,有些函数即使被inline修饰也会被编译器忽略掉。

for(int i=0; i<1000; i++){
	for(int j=0; j<100000; j++0)
	{//...}
}

for(int i=0; i<100000; i++){
	for(int j=0; j<1000; j++0)
	{//...}
}
//第一种更优,因为外层循环跳转较少

//笔试题:C中malloc()/free()和C++中new/delete的区别?(见下)

九、C++的动态内存分配

1、回顾C中动态内存分配

  • 1)分配:malloc() calloc() realloc()
  • 2)释放:free()
  • 3)错误处理:返回值

2、C++中使用操作符动态分配内存

  • 1)分配:new、new[]
  • 2)释放:delete、delete[]
  • 3)错误处理:异常机制(后面会讲)

eg:动态分配一块内存,保存一个整型数

int *pi = (int *)malloc(4);//分配
*pi = 1234;//使用
//......
free(pi);//释放
pi = NULL;
//----------------------------------------
int *pi = new int;//分配
*pi = 1234;//使用
//......
delete pi;//释放
pi = NULL;

eg:动态分配一块内存,保存10个整型数

int *pi = (int *)malloc(40);//分配
//......
free(pi);//释放
pi = N ULL;
//---------------------------------------
int *pi = new int[10];
//......
delete[] pi;//释放
pi = NULL;

08new.cpp

#include <iostream>
using namespace std;

int main(void){
	int *pi = new int;
	*pi = 1234;
	cout << *pi << endl;
	delete pi;//防止内存泄漏
	pi = NULL;//防止野指针
	
	//分配内存时初始化
	int *pi2 = new int(4321);
	(*pi2)++;//*pi2++这样写会出现未知错误
	cout << *pi2 << endl;//4322
	delete pi2;
	pi2 = NULL;

	//new数组
	/*
	int *parr = new int[10];
	for(int i=0; i < 10; i++){
		parr[i] = i + 1;
		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;
}

十、C++的引用(Reference)

1、定义

  • 1)引用就是某个变量的别名,对引用的操作与对变量操作完全相同。
  • 2)语法:类型 &引用名 = 变量名;
    注:
    • a、引用在定义时必须要初始化,初始化以后绑定的变量的不能再修改。int &b;//error
    • b、引用的类型与初始化时绑定的变量类型要一致。
      eg:
int a = 123;
int &b = a;//b就是a的引用(别名)
b++;
cout << a << endl;//124

int c = 321;
b = c;//将c的值赋值给b,等价赋值给a
cout << a << endl;//321

09reference.cpp

#include <iostream>
using namespace std;

int main(void){
	int a = 10;
	int &b = a;//b引用a,b就是a的别名
	cout << "&a=" << &a << ",a=" << a << endl;
	cout << "&b=" << &b << ",b=" << b << endl;

	b++;
	cout << "a=" << a << endl;//11
	cout << "b=" << b << endl;//11
	a++;
	cout << "a=" << a << endl;//12
	cout << "b=" << b << endl;//12
	
	//引用定义时必须初始化
	//int &r;//error报错(不初始化)

	int c = 66;
	b = c;//将c赋值给b(a), 而不是修改引用目标
	cout << "a=" << a << endl;//66
	cout << "b=" << b << endl;//66

	//引用类型和绑定的目标类型要一致
	//double &d = c;
	
	return 0;
}

2、常引用

  • 1)定义引用时加const修饰,即为常引用,不能通过常引用修改引用的目标。
    • const 类型 &引用名 = 变量名;
    • 类型 const &引用名 = 变量名;
      这两种语法的作用是一样的。
      eg:
int a = 1000;
const int *pa = &a;
int const *pa = &a;
int a = 0;
const int &b = a;//b就是a的常引用
b++;//error
a = 100;
cout << b << endl;//100
  • 2)普通引用只能引用左值,而常引用也叫万能引用,既能引用左值也能引用右值。
int a = 100;
int &b = a;//ok
int &b = 100;//error

const int &b = a;//ok
const int &b = 100;//ok
  • 3)关于左值和右值
    • 左值:可以放在赋值运算符(=)左侧,一般普通的变量都是左值

      • 普通变量
      • 赋值表达式结果
      • 前++、–表达式
    • 右值:只能放在赋值运算符(=)右值,一般常量都是右值

      • 常量
      • 大多数的表达式结果
      • 函数返回临时变量(将亡右值)

constRef.cpp

#include <iostream>
using namespace std;

int main(void){
	//普通引用不能引用右值
	//int &r = 100;//error
	
	//常引用既可以引用左值也可以引用右值
	const int &r = 100;//ok
	cout << r << endl;//100
	return 0;
}

3、引用型函数参数

  • 1)将引用用于函数的参数,这时形参就是实参的别名,可以通过形参直接修改实参的值,同时避免数值传递过程,减小函数调用开销。eg:(02refArg.cpp)
  • 2)引用型参数有可能意外修改实参的值,如果不希望修改实参本身,可以将形参定义为常引用,提高传参效率的同时还可以接收常量型的实参。eg:(03refArg.cpp)

02refArg.cpp

#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;
	cout << "a=" << a << ",b=" << b <<endl;
	//swap1(&a, &b);
	swap2(a, b);
	cout << "a=" << a << ",b=" << b << endl;
	return 0;
}

03refArg.cpp

#include <iostream>
using namespace std;

struct Student{
	char name[128];
	int age;
};
void print(const Student &s){//加const关键字可以防止函数修改引用目标
	cout << s.name << ',' << s.age << endl;
}
int main(void){
	Student student = {"zhang", 28};
	print(student);//zhang,28
	return 0;
}

4、引用型函数返回值

  • 1)可以将函数的返回值声明为引用,避免返回值所带来的内存开销。
  • 2)一个函数返回类型被声明为普通引用,那么函数返回值是一个左值。
  • 3)如果不希望函数返回值直接返回左值,可以返回一个常引用。(const)
    注: 不要从函数中返回局部变量的引用,因为所引用的变量内存会在函数返回以后被释放,但是可以返回成员变量、静态变量、全局变量的引用。

eg:

	int func(void){
		static int a = 100;
		return a;//int tmp = a;实际返回结果是tmp
	}
	//----------------------------------------------
	int &func(void){
		static int a = 100;
		return a;//没有tmp,实际返回的就是a的自身
	}

04refReturn.cpp

#include <iostream>
using namespace std;

struct A{
	int data;
	/*const*/ int &func(void){
		return data;
	}

	//不要返回局部变量的引用,会出现警告
	/*
	int &foo(void){
		int a = 123;
		return a;
	}
	*/
};
int main(void){
	A a = {100};
	//等价于a.data = 200;
	a.func() = 200;//ok
	cout << a.data << endl;//200
	return 0;
}

//笔试题:指针和引用的区别?

5、引用和指针

  • 1)如果从C角度去看引用,其本质就是指针,但是再C++中建议是使用引用而不是指针。
    eg:
	int a = 100;
	int &ra = a;
	int  *const pa = &a;
	//*pa <=等价=> ra
  • 2)指针可以不做初始化。其目标可以随意改变(指针常量除外);而引用必须初始化,而且其引用目标不能再改变。
    eg:
	int a = 100, b

以上是关于Review cpp day02的主要内容,如果未能解决你的问题,请参考以下文章

Review cpp day10

Review cpp day09

Review cpp day07

Review cpp day03

Review cpp day08

Review cpp day06