目录三
十.C++的引用(Reference)
3.引用型函数参数
①可以将引用用于函数的参数,这时形参将是实参的别名,可以通过形参直接修改实参变量,同时还可以避免参数值传递的过程,减小函数调用开销,提高代码执行效率。
②引用型参数有可能意外的修改实参,如果不希望修改实参本身,可以将参数定义为常引用,提高传参效率的同时还可以接收常量型的实参。
eg.1
#include <iostream>
using namespace std;
/*这是C的做法*/
/*void swap1(int* x,int* y){
*x = *x ^ *y;
*y = *x ^ *y;
*x = *x ^ *y;
}*/
/*这是C++的做法*/
// 相当于是给a、b起了个别名int& x=a,int& y=b
void swap2(int& x,int& y){
x = x ^ y;
y = x ^ y;
x = x ^ y;
}
void swap3(const char*& x,const char*& y ){
const char* tmp = x;
x = y;
y = tmp;
}
int main(void){
int a = 3;
int b = 5;
cout << "a=" << a << ",b=" << b << endl;
//swap1(&a,&b);
swap2(a,b);
cout << "a=" << a << ",b=" << b << endl;
//练习:使用引用参数,实现两个const char*字符串的交换
const char* s1 = "wangjl";
const char* s2 = "yangjian";
swap3(s1,s2);
cout << "s1=" << s1 << endl;//yangjian
cout << "s2=" << s2 << endl;//wangjl
return 0;
}
4.引用型函数返回值
①可以将函数的返回类型声明为引用,这时函数的返回结果就是return后面数据的别名,可以避免函数返回值所带来的的内存开销,提高代码执行效率.
②如果函数返回结果是一个普通(左值)引用,那么该函数调用表达式结果就也是左值.
(注:不要从函数中返回局部变量的引用,因为所引用的内存会在函数返回以后被释放,使用非常危险!但是可以从函数中返回成员变量、全局变量、静态变量的引用。 )
#include <iostream>
using namespace std;
struct A{
int data;
int& func(void){
return data;
}
int& func2(void){
int num = 100;
return num;//危险!
}
};
int main(void){
A a = {100};
cout << a.data << endl;//100
//a.data = 200
a.func() = 200;//ok
cout << a.data << endl;//200
return 0;
}
5.引用和指针
①从C语言角度看引用的实现,其本质就是指针,但是C++开发中推荐使用引用而不是指针.
int i = 100;
int* const pi = &i;
int& ri = i;
*pi <==> ri
②指针可以不做初始化,其指向的目标可以修改(指针常量除外);而引用必须初始化,一旦初始化其绑定目标的不能再修改。
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,不是修改引用目标,仅是赋值操作
③可以定义指针的指针(二级指针),但是不能定义引用的指针(了解)
int a;
int* p = &a;
int** pp = &p;//ok,二级指针
-----------------------
int& r = a;
int&* pr = &r;//error,引用的指针
int* pr = &r;//ok,但仅是普通指针
④可以定义指针的引用(指针变量的别名),但是不能定义引用的引用(了解)
int a;
int* p = &a;
int* & rp = p;//ok,指针的引用
-----------------------
int& r = a;
int& & rr = r;//error,引用的引用
int& rr = r;//ok,但仅是普通的引用
⑤可以定义指针数组,不能定义引用数组,但可以定义数组引用(数组的别名)、了解
int a=10,b=20,c=30;
int* parr[3]={&a,&b,&c};//ok,指针数组
int& rarr[3]={a,b,c};//error,引用数组
-------------------------
int arr[3] = {a,b,c};
int (&rarr)[3] = arr;//数组引用
⑥和函数指针也可以,可以定义函数引用(函数的别名),其语法特性和函数指针一致(了解)
void func(int a,int b){}
int main(void){
void (*pfunc)(int,int) = func;//ok,函数指针
void (&rfunc)(int,int) = func;//ok,函数引用
pfunc(...);
rfunc(...);
}
十一.类型转换
1.隐式类型转换
char c = 'a';
int i = c;//隐式
----------------
void func(int i){}
func(c);//隐式
---------------
int func(void){
char c = 'a';
return c;//隐式
}
2.显式类型转换
2.1 C++兼容C语言的强制转换
int i = (int)c;//C风格强制转换
int i = int(c);//C++风格强制转换
2.2 C++增加了四种操作符形式的类型转换
① 静态类型转换:static_cast 语法:
目标变量 = static_cast<目标类型>(源类型变量);
适用场景:主要用于将void*转换为其它类型的指针
#include <iostream>
using namespace std;
int main(void){
int* pi = NULL;
//char c = (int)pi;//C风格强制转换
char c = int(pi);//C++风格强制转换
//静态类型转换
//char c2 = static_cast<int>(pi);//不合理
void* pv = pi;
pi = static_cast<int*>(pv);//合理
return 0;
}
②动态类型转换:dynamic_cast
语法: 目标变量 = dynamic_cast<目标类型>(源类型变量);
适用场景:只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。
③(去)常类型转转:const_cast
语法: 目标变量 =const_cast<目标类型>(源类型变量);
适用场景: 主要用于去除指针或引用的const属性
#include <iostream>
using namespace std;
int main(void){
/* volatile修饰变量表示易变的,告诉编译器每次
* 使用该变量都要内存中重新读取,而不是取寄存
* 器中副本,防止编译器优化引发的错误结果.
* */
volatile const int ci = 100;
int* pci = const_cast<int*>(&ci);
*pci = 200;
cout << "ci=" << ci << endl;//200
cout << "*pci=" << *pci << endl;//200
cout << "&ci=" << (void*)&ci << endl;
cout << "pci=" << (void*)pci << endl;
return 0;
}
④重解释类型转换:reinterpret_cast
语法: 目标变量 = reinterpret_cast<目标类型>(源类型变量);
适用场景:
–》任意类型指针或引用之间的显式转换
–》在指针和整型数之间的显式转换
#include <iostream>
using namespace std;
int main(void){
//"\000"->'\0'
char buf[] = "0001\00012345678\000123456";
struct Http{
char type[5];
char id[9];
char passwd[7];
};
Http* ph = reinterpret_cast<Http*>(buf);
cout << ph->type << endl;//0001
cout << ph->id << endl;//12345678
cout << ph->passwd << endl;//123456
return 0;
}
eg:已知物理内存地址0x12345678,向存放数据100?
int* paddr = reinterpret_cast<int*>(0x12345678);
*paddr = 100;
小结:
1.慎用宏,可以使用const、enum、inline
#define PAI 3.14
--> const double PAI = 3.14;
#define SLEEP 0
#define RUN 1
#define STOP 2
--> enum STATE{SLEEP,RUN,STOP};
#define Max(a,b) ((a)>(b)?(a):(b))
--> inline int Max(int a,int b){
return a>b ? a:b;
}
2.变量随用随声明同时初始化
3.尽量使用new/delete取代malloc()/free()
4.少用void*、指针计算、联合体、强制转换
5.尽量使用string表示字符串,少用C风格的字符串char*/char[]
十二.类和对象
1.什么是对象
万物皆对象,任何一种事物都可以看做是对象.
2.如何描述对象
通过对象的属性(名词、数量词、形容词)和行为(动词)来描述对象。
3.面向对象程序设计
对自然世界中对象的观察引入到编程实践中一种理念和方法,这种方法被称为"数据抽象",即描述对象时把细节东西剥离出去,只考虑一些有规律的、一般性、统一性的东西。
4 什么是类
类是多个对象的共性提取出来定义的一种新的数据类型,是对 对象属性和行为的抽象描述。
现实世界 类 虚拟世界
具体对象–抽象–》属性/行为–实例化–》具体对象
十三.类的定义和实例化
1.类的一般语法形式
struct/class 类名:继承方式 基类,...{
访问控制限定符:
类名(形参表):初始化列表{...}//构造函数
~类名(void){...}//析构函数
返回类型 函数名(形参表){...}//成员函数
数据类型 变量名;//成员变量
};
2.访问控制限定符
①public:公有成员,任何位置都可以使用
②private:私有成员,只有类自己的成员函数才能使用
③protected:保护成员(后面讲)
(注:struct定义类默认的访问控制属性是public,class定义类默认的访问控制属性是private)
eg.
struct/class XX{
int a;//默认
private:
int b;//私有成员
public:
int c://公有成员
int d;//公有成员
private:
int e;//私有成员
};
eg.
#include <iostream>
using namespace std;
//原来:结构体
//现在:类
//struct Student{//默认:public
class Student
{//默认:private
public:
//行为:成员函数
void eat(const string& food){
cout << "我在吃" << food << endl;
}
void sleep(int hour){
cout << "我睡了" << hour << "小时"<<endl;
}
void learn(const string& course){
cout << "我在学" << course << endl;
}
void who(void){
cout << "我叫" << m_name << ",今年" <<
m_age << "岁,学号是" << m_no << endl;
}
public:
/* 类中私有成员在外部不能直接访问,但是可以提
* 供类似如下的公有函数来间接访问,在该函数中
* 可以对非法数据加以限定,控制业务逻辑合理性
* 这种编程思想就是封装!*/
void setName(const string& newName){
if(newName == "二"){
cout << "你才二" << endl;
}
else{
m_name = newName;
}
}
void setAge(int newAge){
if(newAge < 0){
cout << "非法的年龄" << endl;
}
else{
m_age = newAge;
}
}
void setNo(int newNo){
if(newNo < 0){
cout << "非法的学号" << endl;
}
else{
m_no = newNo;
}
}
private:
//属性:成员变量
string m_name;
int m_age;
int m_no;
};
int main(void){
//原来:定义结构体变量
//现在:创建对象/实例化对象/构造对象
Student s;
/*s.m_name = "张飞";
s.m_name = "二";
s.m_age = 28;
s.m_no = 10011;*/
s.setName("二");
s.setName("张翼德");
s.setAge(-1);
s.setAge(29);
s.setNo(-1);
s.setNo(10086);
s.who();
s.eat("五香牛肉干");
s.sleep(8);
s.learn("面向对象编程");
return 0;
}