【C++的空指针、C++字符串类型、面向对象的编程与类的设计、构造、析构函数】

C和C++的NULL

C头文件说明了空指针的二义性

#undef NULL
#if defined (___cplusplus)
#define NULL 0
#else
#define NULL((void *)0)
#endif

字面常量0的类型既可以是一个整型,也可以是一个无类型指针(void * ) 。C++11标准用nullptr 解决二义性问题

typedef decltype(nullptr) nullptr_t; 

例子1

using namespace std;
void func(char* p) {}
void func(int x) {}
int main()
{
	func(NULL);//0
	return 0;
}

例子2

int a =NULL;  //a 的值是0了。
int b= nullptr  ; //报错
char *p=nullptr;
if(p==NULL)//c++不要这样写
if(p==nullptr)

注意: 1、nullptr 是C11新引入的关键字,是一个所谓"指针空值类型"的常量,在C++程序中直接使用。 2、在C11 中 , sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同都( 4,或 8)。
2、为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

typedef:合法变量的定义变成类型 的定义

结构体名和结构体类型名在C和C++上的区别

using

typedef int Array[10];
//typedef double Array[20];
//typedef double Array[100];
using iArray =int[10];
using cArray =char[10];

我们可以看见,用typedef无法把权利交给用户。
怎么把权利给用户更灵活呢?请看如下代码:

template<class T,size_t N>
using Array=T[N];
int main()
{
Array <char,10>ar;
Array<double,20>br;
}

通过模板 ,我们可以在主函数中自定义想要的类型和大小,非常方便。
当然我们也可以把模板初始化一下:

template<class T=int,size_t N=10>
using Array=T[N];
int main()
{
Array <ar;
}
//用类型名定义一个函数指针
using pfun=int(*)(int ,int);

函数名的给法:
1.int add(int,int)函数的声明
2.int add(int a,int b)
{} //函数的定义
3.编译连接之后,或运行时标识名被地址替代!!!
4.add(12,23)加括号代表要 调动函数
5.函数名后没有括号代表着函数的地址。

int (*pfun)(int,int)=add;

C++字符串替代字符数组

using namespace;
int main()
char stra[]={"hello"};
int n=strlen(stra);
for(int i=0;i<n;++i)
{
   if(stra[i])
   {
   }
}
strcat(stra,"hello");//str7个字节,再链接就越界了。所以字符数组存放字符串不方便

下面代码通过string 关键字使得字符串操作更加灵活

#include<string>
//std::string s1="tulun";
string s1 ="tulun";  // 字符串  //string  字符串类型
  // s1.length(); // s1.size();
  int n=s1.size();
  for(int i = 0;i<s1.size();++i)
 {
    cout<<s1[i];
 }
  cout<<s1<<endl;
  s1 = "hello";
  cout<<s1<<endl;
  s1+="tulun";
  cout<<s1<<endl; 
  cin>>s1; // new data // 空格为中止符
  cout<<s1<<endl;
  //length  capacity
  //stra+strb  链接
  //stra+"hello"+strb
 
}

string stra=“123”;
string strb=“helo”;
//stra+strb 链接
stra= stra+“hello”+strb;

int main()
{
string s1 = "tulun";  // 字符串  //string  字符串类型
	string s2 = "123";
	const char* p = s2.data();//把s2地址给p;
	cout << s2.data() << endl;//cout << *(s2.data()*) << endl; 结果打印不出来   ??
	const char* cp = s2.c_str();
	return 0;
	}

回去找到C++接口函数

实例:从以下程序看C++中的sizeof()

int main()
{
	std::string stra;
	typedef struct Test
	{
		char* p;
	}*Test1;
	Test arr;
		cout << "sizeof(arr):" << sizeof(arr) << endl;
		arr.p = (char*)malloc(18);
		cout << "sizeof(arr):" << sizeof(arr) << endl;
		strcpy(arr.p, "hellooooooo");
		cout << "sizeof(arr):" << sizeof(arr) << endl;
		//cout << stra.length() << endl;
	
}

在这里插入图片描述

在C++中,sizeof(str)的大小不是str中存放字符总数的大小 而是str空间的大小。

int main()
{
std::string stra;
for(int i=0;i<20;++i)
{
cout<<"sizeof(stra):"<<sizeof(stra)<<endl;
stra+="hello";
cout<<stra<<endl;
cout<<stra.length()<<endl;
}
}

在这里插入图片描述

面向对象的编程
1.理解面向对象的编程
在这里插入图片描述
3.类型设计与实例化对象

封装(Encapsulation)是面向对象程序设计最基本的特性,把数据(属性)和函数(操作)合成
一个整体,这在计算机世界中是用类与对象实现的。

class 类型名称
{
public:
  成员列表1;
protected:
  成员列表2;
private:
  成员列表3;
};//最后的分号不可少;注意:所有说明都有分号

访问限定符(access specifier)有三种: public (公共的), private (私有的)和
protected(保护的)。第一种说明的类型所产生的的实体成员能从外部进行访问,另外两种说明的类型所产生的的实体(对象)成员不能从外部进行访问。每种说明符可在类体中使用多次。它们的作用域是从该说明符出现开始到下一个说明符之前或类体
结束之前结束。

如果在类体起始点无访问说明符,系统默认定义为私有( private )。访问说明符 private (私有 的)和 protected
(保护的)体现了类具有封装性(Encapsulation)。

STRUCT结构体属性的可访问性默认是公有的,而类的可访问性默认是私有的

属性设计成私有 ,方法设计成公有。
属性设计成公有,对象失去 了封装特性。

成员函数

类型设计的更关键部分是对数据成员的操作,用函数来完成:

public://可以在类中定义函数,系统默认为INLINE函数

class CGoods
{
private:
  char Name[21];
  int Amount;
  float Price;
  float Total_value;
public://可以在类中定义函数,系统默认为INLINE函数
  void RegisterGoods(char [], int,float); //输入数据
  // inine void RegisterGoods(char [], int,float); //可以加inline
  void CountTotal(void);     //计算商品总价值
  void GetName(char[]);      //读取商品名
  int GetAmount(void);      //读取商品数量
  float GetPrice(void);      //读取商品单价
  float GetTotal_value(void);    //读取商品总价值
};
void CGoods::RegisterGoods(const char*,int,float)//可以在类外定义,但要署名在CGoods 内
{
}

对象的创建与使用

对象是类的实例(instance)。声明一种数据类型只是告诉编译系统该数据类型的构造,并没有预定内存。类只是一个样板(图纸),以此样板可以在内存中开辟出同样结构的实例–对象。 创建类的对象可以有两种常用方法。

int a;//内置类型定义变量
CGoods  Car; //类型 创建了一个对象。
int main()
{
  CGoods tea;//开始定义对象。设计了一个tea的类型,还不是对象,因为还没开辟内存(实体),
  CGoods book;
  tea.RegisterGoods("black_tea", 12, 560);
  tea.CountTotal();
  book.RegisterGoods("Thinking In C++", 20, 128);
  book.CountTotal();
  return  0;
}

CGoods tea 这条语句,之后调动构造函数才是真的创建了对象

注意: 创建若干个对象 操作公有成员 。一个公共代码区 this指针的根本目的是节省空间 ,方法是用list指针指向对象。

this指针
C++对象模型讨论:
方案一:各对象完全独立地安排内存的方案
在这里插入图片描述

方案二:各对象的代码区共用的方案:
在这里插入图片描述

我们当然要选用第二种方案,所以要用到一个指针来找到那个对象。.

创建对象过程中编译器的动作
1、对数据成员(属性)进行扫描(描述表)
2、识别成员函数 (函数的声明)
3、改写类函数 ,用this指针指向对象(形参表加入this指针,函数代码中的每个属性前加this)

实际情况由如下代码说明:

//float GetPrice(GCgoods *this)//this 是指向对象的地址,把这个地址传到函数中,进而每个对象都可以辨别。
	float GetPrice() {
		return this->Price;
	}
	int main()
{
	CGoods car;
	car.GetPrice(&car);
}

为什么给this指针而不是引用呢
(自己理解:因为引用的本质就是指针。)

构造函数(创建资源)

C++的类中有6个默认的函数,他们分别是:
1.构造函数
2.析构函数
3.拷贝构造函数
4.赋值运算符重载函数
5.取地址操作符重载函数
6.const修饰的取地址操作符的重载函数
7.移动构造
8.移动赋值

数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该在且
仅在定义对象时自动执行一次。称为构造函数(constructor) 。
构造函数用途:1)创建对象,2)初始化对象中的属性,3)类型转换。

有对象一定要有空间,有空间不一定有对象

构造函数的定义与使用
构造函数是特殊的公有成员函数(在特殊用途中构造函数的访问限定可以定义成私有或保护),其
特征如下:
1.函数名与类名相同。
2.构造函数无函数返回类型说明。注意是没有而不是void,即什么也不写,也不可写void。实际上
构造函数有返回值,返回的就是构造函数所创建的对象。
3.在程序运行时,当新的对象被建立,该对象所属的类构造函数自动被调用,在该对象生存期中也
只调用这一次。
4.构造函数可以重载。严格地讲,类中可以定义多个构造函数,它们由不同的参数表区分,系统在
自动调用时按一般函数重载的规则选一个执行。
5.构造函数可以在类中定义,也可以在类中声明,在类外定义。
6.如果类说明中没有给出构造函数,则C++编译器自动给出一个缺省的构造函数:
类名(void) { }
但只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。只要构造函数是无参的或
者只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数,并且缺省的构造函数只能有一个 。

以下代码需要说明的是,对象e的大小是1个字符的站位符;

using namespace std;
class Empty
{
};
int main()
{
	Empty e;
	cout << sizeof(e) << endl;
	return 0;
}

圈1:1个字符的占位符

调动构造函数才是真的创建了对象

C和C++的另外某些区别:

(1).C语言 有空间就能操作

int main()
{
	char buff[10];
	int* p = (int*)buff;
	*p = 100;
	printf("%d", buff[0]);
	return 0;
}
有空间就能实现面向过程编程。

(2)C++ 有空间,在创建对象后 才能操作(有对象一定要有空间)

	CGoods() 
		: Name{} //const char* name, int amount, float price
	{
		
		cout << "Create Object: Complex() " << endl;
	}
};

void CGoods::print()//const char* name, int amount, int price)
{
	cout <<"Name:"<<Name  << endl;
}
int main()
{
	CGoods car;
	CGoods* ip = (CGoods*)malloc(sizeof(car));
	ip->print();

结果打印的Name是随机值,因为只有空间没有对象。

当然也不可以把对象赋值给空间。

CGoods car("byd");
	CGoods* ip = (CGoods*)malloc(sizeof(car));
	*ip = car;//error
	ip->print();

那怎么办?如何用类指针来使用类里的方法呢?我们可以用定位new来处理:new(10) CGgoods()

1.带参数的构造函数(初始化的情况)

CGoods(const char *name, int amount, float price)
: Name{name}, Amount{amount}, Price{price}//const char name, int amount, float price
{
// Name=name;
//Amount=amount;
//Price=price;
strcpy_s(Name, 20, name);
cout << "Create Object: Complex() " << endl;
}
没有初始化就需要写注释的三个语句。

using namespace std;
const int len = 20;
class  CGoods
{
private:
	
	char Name[len];
	int Amount;
	float Price;
	float Total;
public:
	void RegisterGoods(const char*, int, float);
	void CountTotal(const char*, int, float)
	{
		Total = Price * Amount;
	}
	const char* GetName()
	{
		return Name;
	}
	int GetAmount() { return Amount; }
	float GetPrice() {
		return Price;
	}
	float GetTotal() {
		return Total;
	}
	void  print();
	CGoods(const char *name, int amount, float price) 
		: Name{*name}, Amount{amount}, Price{price}//const char* name, int amount, float price
	{
	// Name=name;
	//Amount=amount;
	//Price=price;
		strcpy_s(Name, 20, name);
		cout << "Create Object: Complex() " << endl;
	}
};
void CGoods::RegisterGoods(const char* name, int amount, float price)
{
	strcpy_s(Name, 20, name);
	Amount = amount;
	Price = price;

}
void CGoods::print()//const char* name, int amount, int price)
{
	cout <<"Name:"<<Name <<"  amount :  "<<Amount<<"  price :  "<< Price << endl;
}
int main()
{
	//const char arr[] = "byd";
	//char arr[] = {  };
	CGoods car ("byd", 12, 12.25);
	//car.RegisterGoods("byd", 12, 1288);
	//CGoods("byd", 12, 1288);
	car.print();
}

2.缺省的构造函数:

	CGoods() 
		//: Name{}, Amount{}, Price{}//const char* name, int amount, float price
	{
		
		cout << "Create Object: Complex() " << endl;
	}
};
void CGoods::print()//const char* name, int amount, int price)
{
	cout <<"Name:"<<Name <<"  amount :  "<<Amount<<"  price :  "<< Price << endl;
}
int main()
{
	CGoods car;
	car.print();
}

以上代码,未初始化的构造函数打印结果是随机值
在这里插入图片描述

CGoods() 
	: Name{}, Amount{}, Price{}
	{
		
		cout << "Create Object: Complex() " << endl;
	}
};
void CGoods::print()//const char* name, int amount, int price)
{
	cout <<"Name:"<<Name <<"  amount :  "<<Amount<<"  price :  "<< Price << endl;
}
int main()
{
	CGoods car;
	car.print();
}

以上代码 有初始值
在这里插入图片描述

构造函数的一些特别情况
int main()
{
Complex cc = Complex(3,4);
Complex cd{5,6};
Complex ce(); //不会创建对象“ce”,而会被认为是函数的声明
return 0;
}

构建顺序是类声明的顺序,见下图

在这里插入图片描述

析构函数的定义(获取的资源释放掉)

当定义一个对象时,C++自动调用构造函数建立该对象并进行初始化,**那么当一个对象的生命周期结束时,C++也会自动调用一个函数注销该对象并进行善后工作,**这个特殊的成员函数即析构函数 (destructor):

  1. 构函数名与类名相同,但在前面加上字符‘~’,如 : ~CGoods()。
  2. 析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。
  3. 一个类有一个也只有一个析构函数,这与构造函数不同。
  4. 对象注销时,系统自动调用析构函数。
  5. 如果类说明中没有给出析构函数,则C++编译器自动给出一个缺省的析构函数。 对象不能创建对象,但可杀死自己(对象)。
Complex c1;
//c1.Complex //error,对象不能建立对象
c1.~Complex //但可以杀死自己

对象的生存期

1.函数中创建对象的情况(对象随着函数的结束而析构)

using namespace std;
class Int
{
private:
	int ab;
	
public:
	Int(int x) : ab(x) {
		cout << "Int CREATE: " << this << endl;
	}
	~Int() {
		cout << "Int Destroy: " << this << endl;
	}
	void fun();
	void Print()
	{
		
		cout << ab << endl;
	}
};

void fun()
{
	Int a(10);
	
	a.Print();
}
int main()
{
	
	for (int i = 0; i < 10; ++i)
	{
		fun();
	}
	return 0;
}

在这里插入图片描述

2.函数中的构造函数加上static的情况


void fun()
{
	static Int a(10);
	a.Print();
}
int main()
{
	
	for (int i = 0; i < 10; ++i)
	{
		fun();
	}
	return 0;
}

在这里插入图片描述

我们可以看到这期间只构建了一个对象。为什么?

在栈区开辟对象空间时,另外设置一个标志位,默认为零。然后再构建对象时改为1。
再次调动函数发现标志是1,那么系统将不在构建一个新的对象。

用指针把“信号”修改,结果发生了改变
所以a一直在构建

生存期与可见性
编译连接阶段 :可见性
运行时:生存期

在这里插入图片描述
c在编译连接过程都没通过,生存期就不用讨论了。

局部变量区为什么称为栈区?

在局部变量中申请变量时和进栈是相似的动作,和销毁时和出栈是相似的动作。

Int a(1);
Int *ip=(int*)malloc(sizeof(Int));//1

*ip=a;//不能把对象赋值给空间

只有空间没有对象,所以我们需要定位new建立对象:

Int a(1);
Int *ip=(int*)malloc(sizeof(Int));//1
new(ip) Int(10);//定位new建立对象
ip->Show();
ip->Print();
ip->~Int();
free(ip);
ip=nullptr;
return 0;

ip->~Int(); //把ip资源释放

free(ip); //把ip的空间释放

int main()
{
Int *ip=new Int(10);
ip->Print();
delete ip;
ip=nullptr;

delete ip; // 第一步:析构 (ip->~Int ( ) ),第二步:释放空间(free(ip));

Int *ip=new Int[10]{1,2,3,4,5,6,7,8,9,10};
Int *ip=new Int[10]{{1,2},{3,4},{5,6},7,8,9,10};//构造函数 有两个参数时

Int *ip=new Int[10]; delete [ ] ip;//申请一组对象,释放时也要释放一组对象,否则发生系统的崩溃。以下的delete ip 是错误代码:

在这里插入图片描述

从以上内存显示得知, 一共开辟了40个字节的空间,加上4个对象的占位,一共44个字节。

上述程序类里提供了析构函数,delete ip时 程序会出现崩溃(为什么?)类型没有明确给出析构函数时,new 和 delete 可以混用计数值不会检查析构对象的个数

在这里插入图片描述
蓝色框是计数值(以后方便用于析构的次数)

以下程序中,如果有析构函数,那么delete p操作将引起异常(系统将上下越界标记当做对象释放掉,执行时发生无限的释放。)
在这里插入图片描述

在new申请空间时,不允许抛出异常时(nothrow),才进行判空检查 : if(nullptr==ip)return 1;

int n=10;
int *ip=(int *)::operator new(sizeof(int)*10,nothrow);//有空间没对象
if(nullptr==ip)return 1;//不允许抛出异常时,才进行判空检查
for(int i=0;i<n;++i)
{
new(&ip[i])Int (i);
}
///
///
for(int i=0;i<n;++i)
{
new(&ip[0]) Int~ (i);
}
::operator delete(ip);
ip=nullptr;
return 0;

成员方法的设计

using namespace std;
class Complex
{
private:
       int Real;
       int Image;
public:
       Complex(int);
       void SetReal(int r)
       {
       Real=r;}
       void SetImage(int i){Image;}

//方法调用时 调用的是对像的行为

在这里插入图片描述

以下是否可以编译通过?


void fun()
{

}

常方法不能调动普通方法 ,这是不安全的

这里是引用

在这里插入图片描述
普通对象 优先对应普通方法
常对象 优先对应常方法

class Int {
private:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
作业:C++写栈的代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值