C++第二天

八、C++的动态内存分配
new运算符用于动态内存分配,delete运算符用于动态内存释放。
C语言:
int* p = (int*)malloc (sizeof (int));
*p = 100;在堆上
free (p);
C++语言<兼容上面的>:
int* p = new int<返回地址>;
*p = 100;
delete p;(释放p指向的内存)
int* pa = new int[10];
pa[0] = 10;
pa[1] = 20;
...
delete[] p;(必须加[])
new.cpp
九、C++的引用
1.引用就是别名。
int a = 10;
int& b = a; // b就是a的别名
b++;
cout << a << endl; // 11
2.将引用用于函数的参数,可以修改实参变量的值,可以减小函数调用的开销,避免虚实结合过程中对实参值的复制。
3.将函数的形参定义为常引用,可以在提高传参效率的同时,防止对实参的意外修改,而且还可以接受常量型的实参对象。
4.不要从函数中返回对局部变量的引用,因为该引用所引用的内存会在函数返回以后随着函数栈一起被释放。
5.引用与指针
1)引用的本质就是指针。
2)指针可以不做初始化,其目标可以在初始化以后随意改变(除非是指针常量),而引用必须做初始化,且一旦初始化其所引用的目标不能再改变。
int a, b;
int* p; // 可以不初始化
p = &a; // p指向a
p = &b; // p指向b
int& r; // error,引用不能不初始化
int& r = a; // r引用a
r = b; // 将b的值赋给a
3)可以定义指针的指针(二级指针),但是不能定义引用的指针。
int a;
int* p = &a;
int** pp = &p; // ok
int& r = a;
int&* pr = &r; // error
4)可以定义指针的引用,但是不能定义引用的引用。
int a;
int* p = &a;
int*& rp = p; // ok
int& r = a;
int&& rr = r; // error
5)可以定义指针数组,但是不能定义引用数组,可以定义数组引用。
int a, b, c;
int* parr[]={&a,&b,&c}; // ok
int& rarr[]={a,b,c}; //error
int arr[] = {1,2,3};
int (&r)[3] = arr; // ok
6)和函数指针一样,也可以定义函数引用,其语法特征与指针完全相同。


第二课  类和对象(上)
一、类型转换运算符
1.静态类型转换:
目标类型标识符 = static_cast<目标类型> (源类型标识符);
int a;
void* pv = &a;
int* pn = static_cast<int*> (pv);
静态类型转换主要用于将void*转换为其它类型的指针。
2.动态类型转换:
目标类型标识符 = dynamic_cast<目标类型> (源类型标识符);
主要用于就有多态特性的父子类对象指针或引用之间的转换。
3.重解释类型转换:
目标类型标识符 = reinterpret_cast<目标类型> (源类型标识符);
int a;
double* p = reinterpret_cast<double*> (&a);
在不相关的指针或引用类型之间相互转换。
4.常类型转换:
目标类型标识符 = const_cast<目标类型> (源类型标识符);
去除一个指针或引用的常属性。
const int a = 10;不能通过a来改
const int* p = &a;
*p = 20; // error
int* p2 = const_cast<int*> (p);
*p2 = 20; // ok
double* p3 = const_cast<double*> (p); // error
const int& r = a;
r = 30; // error
int& r2 = const_cast<int&> (r);
r2 = 30; // ok
二、什么是对象
万物皆对象。
学生对象
属性:年龄、姓名、学号
行为:学习、吃饭、娱乐
手机对象
属性:品牌、价格、分辨率、内存
行为:通话、短信、播放音频视频、游戏、应用
三、类和对象
类:对拥有相同属性和行为的对象进行归纳和抽象。
四、类的定义与使用
1.类的定义
class 类名 {
};
例如:
class Student {
};
2.成员变量:描述属性
class 类名 {
  类型 成员变量名;
};
例如:
class Student {
  string m_name;
  int m_age;
};
3.成员函数:描述行为
class 类名 {
  返回类型 成员函数名 (形参表) {
     函数体;
  }
};
例如:
class Student {
  string m_name;
  int m_age;
  void learn (string lesson) {
    ...
  }
  void eat (string food) {
    ...
  }
};
4.访问控制:
public:公有成员,谁都能访问。
private:私有成员,只有自己能访问。
protected:保护成员,只有自己和自己的子类能访问。
class Student {
public:
  string m_name;
  int m_age;
  void learn (string lesson) {
    ...
  }
  void eat (string food) {
    ...
  }
};
类的缺省访问控制为私有,而结构的缺省访问控制为公有。
同时也建议改成私有的,通过公有的函数来修改,而不是允许直接访问 。
五、构造函数
class 类名{
  函数名(类名)(形参表){
构造函数体;
}
};
构造函数没有返回类型,函数名与类名相同。
构造函数无需而且无法直接调用 。当创建对象的时候这个函数会自动的执行。完成对象的初始化。
#include<iostream>
using namespace std;
class Student{
public:
//构造函数
Student(string name,int age){
m_name=name;
m_age=age;
}
Student(string name){
m_name=name;
m_age=0;
}
Student(void){
m_namge="abd";
m_age=12;
}
void who(void){
cout<<"我叫"<<m_name;
}
private:
string m_name;
int m_age;
}
int main(){
Student s1("abd",13);
Student s3();//编译错误
}
1.构造函数的参数来自创建对象时所提供的构造实参表。
2.构造函数可以通过形参表的不同实现重载,在创建对象的过程中,根据所提供构造实参表的不同,匹配到相适应的构造函数版本。
3.构造函数可以不带任何参数,这样的构造函数通常被称为无参构造函数或缺省构造函数,但,无参构造函数或缺省构造函数未必一定无参。形参可以带缺省值
4.如果一个类没有定义任何构造函数,系统就会自动提供一个无参构造函数,在这个构造函数中完成基本的初始化工作。只要你定义了一个构造函数,不管无参有参,系统都不会再提供这个默认的无参构造函数。
5.构造函数的工作过程:
创建并初始化基类部分。
创建并初始化成员变量。
执行构造函数中的代码。
6.初始化表
//初始化表的用法
#include<iostream>
using namespace std;
chass Student{
public:
Student(string name,int age):m_name(name),m_age(age){}
void who(void){
cout<<m_name;
}
private:
string m_name;
int m_age;
};
可以在定义构造函数的时候通过初始化表指示编译器对各个成员变量的初始化方式。
2.必须使用初始化表的场合:
类中含有缺少无参构造函数的类类型成员或者基类。如:
#include<iostream>
using namespace std;
class Date{
public:
//缺少无参构造函数
Date(int year,int month,int day){
m_year=year;
m_month=month;
m_day=day;}
int m_year;
int m_day;
int m_month;
}
chass Student{
public:
Student(string name,int age):m_name(name),m_age(age){}
void who(void){
cout<<m_name;
}
private:
string m_name;
int m_age;
Date m_birthday;
};
使用初始化表:
Student(string name,int age,int year,int month,int day):m_name(name),m_age(age),m_birthday(year,month,day){}
2.类中含有引用或者常量型的成员变量。
//引用和常量型成员变量的初始化
int g_n=1000;
class A{
public:
A(int c):m_r(g_n),m_c(c){}
void show(void){
cout<<m_r<<','m_c<<endl;


}
private:
int &m_r;
const int m_c;
};
int main(void){
A a(2000);
a.show();
}
3.成员变量的初始化顺序依据被声明的顺序,而非在初始化表的出现顺序。


六 声明与实现分离的类定义
将父类的声明放在.h文件中,而将类的实现放在.cpp文件中。
//student.h
#ifndef _STUDENT_H
#DEFINE _STUDENT_H
#include<string>
using namespace std;
class Student{
public:
Student(const string &name,int age);
void who(void);
private:
string m_name;
int m_age;
};
#endif // _STUDENT_H


//student.cpp
#include<iostream>
#include"student.h"
using namespace std;
Student::Student(const string &name,int age):m_name(name),m_age(age){}
void Student::who(void){
cout<<m_name<<','<<m_age;
}


// 使用
#include"student.h"
int main(void){
Student student(" 张飞",28);
student.who();
return 0;
}
如果一个类的成员函数在类的声明部分进行定义,则缺省为内联函数,如果成员函数在类声明的外部定义,则缺省为非内联,对于后都如果在其声明部分使用inline关键字,则表明建议编译器对该函数做内联处理 。
七 this指针
1.对于一个对象,在其所属类型的成员函数中,都有一个隐含的this指针,该指针指向调用该函数的对象--调用对象。对于构造函数而言,其this指针就指向正在被构造的对象。
2.在构造函数中可以通过this指针区分同名的构造形参和成员变量。
this->name=name;
this->age=age;
3.通过返回*this,实现返回对调用对象自身的引用。满足级联式函数调用的需要。
#include<iostream>
using namespace std;
class integer{
public:
Integer(int data):m_data(data){}
Integer& addadd(void){
m_data++;
return *this;
private:
int m_data;
}
int main(){
integer i(10);
i.addadd().addadd().addadd();//13
i.print();
}
4.通过将this指针做为函数的参数,实现不同对象间的交互。
//通过this指针实现对象间的交互
#include<iostream>
using namespace std;
class Teacher{
public:
void educate(Student* ps){
ps->ask("什么是this指针",this);
cout<<"回答"<<m_answer<<endl;
}
void reply(const string& answer){
m_anser=anser;
}
private:
string m_anser;
};
class Student{
public:
void ask(const string& question,Teacher* pt){
cout<<"问题"<<question<<endl;
pt->reply("this指针就是指向调用对象的指针");
}
int main(void){
Teacher t;
Student s;
t.educate(&s);


}


会出现一个问题:编译不过去。因为Student在Teacher后面声明。即为交叉类问题,你中有我,我中有你。解决:在最前面加class Student;还有解决方案,把函数放在已经定义好的函数后面:
void Teacher::educate(Student *ps){


}
二.常量型成员函数与常量型对象
1.常量型成员函数中的this指针是一个常量指针,以此防止对调用对象的意外修改。
class student{
public:
Student(const string& name="",int age=0):m_name(name),m_age(age){}
void print (void) const{//常量型成员函数
cout<<m_name<<','<<m_age<<endl;
m_age++;//引发编译错误
}
private:
string m_name;
int m_age;
};
但是如何修改呢:
在变量上前加mutable int m_count;
被声明为mutable的成员变量可以在常量型成员函数中被修改。
const指对成员函数起作用。
成员函数构成重载:1.形参表不同,2.常属性不同。常量型成员函数与非常量型成员函数可以构成重载关系,通过常量型成员函数,通过非常型对象调用非常量型成员函数。
using namespace std;
class A{
public:
void foo(void){
cout<<"非常量型的foo函数"<<endl;
}
void foo(void) const{
cout<<"常量型的foo函数“<<endl;
}
};
int main(void){
A a;
a.foo();//非常量版本
const A aa;
aa.foo();//常量版本
const A* p=&a;
p->foo();//常量版本 
const A& r=a;
r.foo();//常量版本
A* pp=const_cast<A*> (&aa);
pp->foo();//非常量版本
}
4.通过常量型对象(或指针,引用)只能调用常量型成员函数,通过非常量型对象优先选择非常量型成员函数,也可以调用常量型成员函数。
三析构函数
1.当一个类类型对象被销毁的时候(局部变量离开作用域,new动态创建堆对象被delete),析构函数被自动执行。
2.语法形式:
class 类名{
~类名(void){
析构函数体。
}
};
析构函数没有参数,不能重载。
#include "stdafx.h"
#include<iostream>
using namespace std;
class A{
public:
A(int a=0):m_a(a){
cout<<"A construction"<<m_a<<endl;
}
//析构函数
~A(){
cout<<"a is disconstructed"<<m_a<<endl;
}
void print(){
cout<<m_a<<endl;
}
public:
int m_a;
};
void foo(){
A a;
a.print();
A* pa=(A*)malloc(sizeof(A));
pa->print();
free(pa);
pa=new A(100);
pa->print();
delete pa;
}
int main(){
foo();
return 0;
}
通过以上这段代码可以了解到以下几点:malloc函数的使用并不会调用构造函数,使用new会调用构造函数,在类创建对象时会调用构造函数,delete使用以删除new所产生的对象时会调用析构函数,当局部变量作用域结束时会调用析构函数。
什么时候需要写析构函数?????
3.如果你在构造函数当中动态了分配资源,那么就需要自己定义析构函数并在其中释放上述资源。(内存,文件,网络资源等),如果没有定义析构函数,那么系统就会提供一个缺省的析构函数 。但是缺省的析构函数只负责释放成员变量,而不翻译动态分配的资源。
class Student{
public:
//构造函数
Student(const char *name,int age):m_age(age),m_name((char*)name){

}
void print(){
cout<<m_age<<" '"<<m_name<<endl;
}
private:
int m_age;
char * m_name;
};
int main(){
char name[128]="飞鸟";
Student a(name,28);
a.print();
strcpy(name,"赵去");
a.print();
return 0;
}
使用以上代码运行会出现一种问题,当主函数name修改后,类对象的值也被修改了。必须申请内存存储值。
//构造函数
Student(const char *name,int age):m_age(age),m_name(strcpy(new char[strlen(name?name:"")+1],name?name:"")){

}
上述代码即可修改该问题,但该代码申请了内存,于是需要重写析构函数,以释放该块内存。
~Student(){
delete[]m_name;
m_name=NULL;
}
4.析构函数的执行过程
1.执行析构函数中的代码
2.释放成员变量 
3.释放基类部分




四.拷贝构造函数与拷贝赋值运算符
1.所谓拷贝构造,就是以拷贝的方式进行构造。
Student s1(```);
Student s2(s1);//拷贝构造
Student  s2=s1;//拷贝构造
构造s1的副本s2.
2.缺省方式的拷贝构造对于基本类型的成员变量,按字节复制,对于类类型的成员变量,执行相应类型的拷贝构造。
但缺省方式下的构造有不适合的场合:
如果一个类中构造函数动态申请了内存,而析构函数释放了内存,在缺省方式下,会导致double free错误,因为两个对象指向了同一块内存。
3.以上拷贝为浅拷贝。两个指针内容完全一样,指向同一块内存。所以需要进行深拷贝。
重新添加一个构造函数:
Student(const Student& that):m_name(strcpy(new char[strlen(that.m_name)+1],that.m_name)),m_age(that.m_age){}


建议经常用引用做形参。否则可能会发生拷贝构造。
3.对于含有手动分配资源的对象缺省拷贝构造函数往往只能实现浅拷贝,为了获得基于深拷贝的完整副本,常常需要自定义拷贝构造函数。
类名(const 类名& that){}


4.拷贝构造的时机
1)直接构造对象副本
2)以对象为参数调用函数
3)从函数中返回对象
4)以对象的方式捕获异常
5.拷贝赋值
Student s1("zhangfei",24);
Student s2("zhaoun",32);
s2=s1;//拷贝赋值
s2.print();
浅拷贝,深拷贝需要自定义拷贝赋值函数
s3=s1//s3.operator=s1;
Student& operator=(const Student& that){
if(&that!=this){
char *name=new char[strlen(that.m_name)+1];
delete[] m_name;
m_name=strcpy(name,that.name);
return *this;
}
}
拷贝复制尽量避免使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值