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):
- 构函数名与类名相同,但在前面加上字符‘~’,如 : ~CGoods()。
- 析构函数无函数返回类型,与构造函数在这方面是一样的。但析构函数不带任何参数。
- 一个类有一个也只有一个析构函数,这与构造函数不同。
- 对象注销时,系统自动调用析构函数。
- 如果类说明中没有给出析构函数,则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++写栈的代码