网站开发的技术简介是什么,盐城网站建设兼职,宁波网站建设费用,制作网页软件列表html代码类和对象的基本概念 C 和 C中 struct 区别 c 语言 struct 只有变量 c语言 struct 既有变量#xff0c;也有函数 类的封装 我们编写程序的目的是为了解决现实中的问题#xff0c;而这些问题的构成都是由各种事物组成#xff0c;我们在计算机中要解决这种问题#x…类和对象的基本概念 C 和 C中 struct 区别 c 语言 struct 只有变量 c语言 struct 既有变量也有函数 类的封装 我们编写程序的目的是为了解决现实中的问题而这些问题的构成都是由各种事物组成我们在计算机中要解决这种问题首先要做就是要将这个问题的参与者事和物抽象到计算机程序中也就是用程序语言表示现实的事物。 那么现在问题是如何用程序语言来表示现实事物现实世界的事物所具有的共性就是每个事物都具有自身的属性一些自身具有的行为所以如果我们能把事物的属性和行为表示出来那么就可以抽象出来这个事物。 比如我们要表示人这个对象在 c 语言中我们可以这么表示 : typedef struct _Person{char name[64];int age;
}Person;
typedef struct _Aninal{char name[64];int age;int type; //动物种类
}Ainmal;
void PersonEat(Person* person){printf(%s 在吃人吃的饭!\n,person-name);
}
void AnimalEat(Ainmal* animal){printf(%s 在吃动物吃的饭!\n, animal-name);
}
int main(){Person person;strcpy(person.name, 小明);person.age 30;AnimalEat(person);return EXIT_SUCCESS;
} 定义一个结构体用来表示一个对象所包含的属性函数用来表示一个对象所具有的 行为这样我们就表示出来一个事物在 c 语言中行为和属性是分开的也就是 说吃饭这个属性不属于某类对象而属于所有的共同的数据所以不单单是PeopleEat 可以调用 Person 数据 AnimalEat 也可以调用 Person 数据那么万一 调用错误将会导致问题发生。 从这个案例我们应该可以体会到属性和行为应该放在一起一起表示一个具有属 性和行为的对象。 假如某对象的某项属性不想被外界获知比如说漂亮女孩的年龄不想被其他人知道 那么年龄这条属性应该作为女孩自己知道的属性或者女孩的某些行为不想让外界知道只需要自己知道就可以。那么这种情况下封装应该再提供一种机制能够给属性和行为的访问权限控制住。所以说封装特性包含两个方面一个是属性和变量合成一个整体一个是给属性和 函数增加访问权限。 封装 1. 把变量属性和函数操作合成一个整体封装在一个类中 2. 对变量和函数进行访问控制 访问权限 3. 在类的内部 ( 作用域范围内 ) 没有访问权限之分所有成员可以相互访问 4. 在类的外部 ( 作用域范围外 ) 访问权限才有意义 public private protected 5. 在类的外部只有 public 修饰的成员才能被访问在没有涉及继承与派生时 private 和 protected 是同等级的外部不允许访问 对象的构造和析构 初始化和清理 我们大家在购买一台电脑或者手机或者其他的产品这些产品都有一个初始设置 也就是这些产品对被创建的时候会有一个基础属性值。那么随着我们使用手机和电脑的时间越来越久那么电脑和手机会慢慢被我们手动创建很多文件数据某一天我们不用手机或电脑了那么我们应该将电脑或手机中我们增加的数据删除掉保护自己的信息数据。 从这样的过程中我们体会一下所有的事物在起初的时候都应该有个初始状态 当这个事物完成其使命时应该及时清除外界作用于上面的一些信息数据。 那么我们 c 中 OO 思想也是来源于现实是对现实事物的抽象模拟具体来说 当我们创建对象的时候, 这个对象应该有一个初始状态当对象销毁之前应该销毁自己创建的一些数据。 对象的初始化和清理也是两个非常重要的安全问题一个对象或者变量没有初始时 对其使用后果是未知同样的使用完一个变量没有及时清理也会造成一定的安 全问题。c 为了给我们提供这种问题的解决方案构造函数和析构函数这两个函数将会被编译器自动调用完成对象初始化和对象清理工作。 无论你是否喜欢对象的初始化和清理工作是编译器强制我们要做的事情即使你 不提供初始化操作和清理操作编译器也会给你增加默认的操作只是这个默认初始化操作不会做任何事所以编写类就应该顺便提供初始化函数。 为什么初始化操作是自动调用而不是手动调用既然是必须操作那么自动调用会 更好如果靠程序员自觉那么就会存在遗漏初始化的情况出现。 构造函数和析构函数 构造函数主要作用在于创建对象时为对象的成员属性赋值构造函数由编译器自动 调用无须手动调用。 析构函数主要用于对象销毁前系统自动调用执行一些清理工作。 构造函数语法 构造函数函数名和类名相同没有返回值不能有 void 但可以有参数。 ClassName(){} 析构函数语法 析构函数函数名是在类名前面加”~” 组成 , 没有返回值不能有 void, 不能有参数 不能重载。 ~ClassName(){} class Person{
public:Person(){cout 构造函数调用! endl;pName (char*)malloc(sizeof(John));strcpy(pName, John);mTall 150;mMoney 100;}~Person(){cout 析构函数调用! endl;if (pName ! NULL){free(pName);pName NULL;}}
public:char* pName;int mTall;int mMoney;
};
void test(){Person person;cout person.pName person.mTall person.mMoney endl;
} 构造函数的分类及调用 按参数类型分为无参构造函数和有参构造函数 按类型分类普通构造函数和拷贝构造函数 ( 复制构造函数 ) class Person{
public:Person(){cout no param constructor! endl;mAge 0;}//有参构造函数Person(int age){cout 1 param constructor! endl;mAge age;}//拷贝构造函数(复制构造函数) 使用另一个对象初始化本对象Person(const Person person){cout copy constructor! endl;mAge person.mAge;}//打印年龄void PrintPerson(){cout Age: mAge endl;}
private:int mAge;
};
//1. 无参构造调用方式
void test01(){//调用无参构造函数Person person1; person1.PrintPerson();//无参构造函数错误调用方式//Person person2();//person2.PrintPerson();
}//2. 调用有参构造函数
void test02(){//第一种 括号法最常用Person person01(100);person01.PrintPerson();//调用拷贝构造函数Person person02(person01);person02.PrintPerson();//第二种 匿名对象(显示调用构造函数)Person(200); //匿名对象没有名字的对象Person person03 Person(300);person03.PrintPerson();//注意: 使用匿名对象初始化判断调用哪一个构造函数要看匿名对象的参数类型Person person06(Person(400)); //等价于 Person person06 Person(400);person06.PrintPerson();//第三种 号法 隐式转换Person person04 100; //Person person04 Person(100)person04.PrintPerson();//调用拷贝构造Person person05 person04; //Person person05 Person(person04)person05.PrintPerson();
} b 为 A 的实例化对象 ,A a A(b) 和 A(b) 的区别 当 A(b) 有变量来接的时候那么编译器认为他是一个匿名对象当没有变量来接 的时候编译器认为你 A(b) 等价于 A b. 注意:不能调用拷贝构造函数去初始化匿名对象,也就是说以下代码不正确: class Teacher{
public:Teacher(){cout 默认构造函数! endl;}Teacher(const Teacher teacher){cout 拷贝构造函数! endl;}
public:int mAge;
};
void test(){Teacher t1;//error C2086:“Teacher t1”: 重定义Teacher(t1); //此时等价于 Teacher t1;
} 拷贝构造函数的调用时机 对象以值传递的方式传给函数参数 函数局部对象以值传递的方式从函数返回(vs debug 模式下调用一次拷贝构造 qt 不调用任何构造) 用一个对象初始化另一个对象 class Person{
public:Person(){cout no param contructor! endl;mAge 10;}Person(int age){cout param constructor! endl;mAge age;}Person(const Person person){cout copy constructor! endl;mAge person.mAge;}~Person(){cout destructor! endl;}
public:int mAge;
};
//1. 旧对象初始化新对象
void test01(){Person p(10);Person p1(p);Person p2 Person(p);Person p3 p; // 相当于 Person p2 Person(p);
}
//2. 传递的参数是普通对象函数参数也是普通对象传递将会调用拷贝构造
void doBussiness(Person p){}
void test02(){Person p(10);doBussiness(p);
}
//3. 函数返回局部对象
Person MyBusiness(){Person p(10);cout 局部 p: (int*)p endl;return p;
}
void test03(){//vs release、qt 下没有调用拷贝构造函数//vs debug 下调用一次拷贝构造函数Person p MyBusiness();cout 局部 p: (int*)p endl;
} [Test03 结果说明 :] 编译器存在一种对返回值的优化技术,RVO(Return Value Optimization). 在 vs debug模式下并没有进行这种优化所以函数 MyBusiness 中创建 p 对象调用了一次构造函数当编译器发现你要返回这个局部的对象时编译器通过调用拷贝构造生成 一个临时 Person 对象返回然后调用 p 的析构函数。 我们从常理来分析的话这个匿名对象和这个局部的 p 对象是相同的两个对象 那么如果能直接返回 p 对象就会省去一个拷贝构造和一个析构函数的开销在 程序中一个对象的拷贝也是非常耗时的如果减少这种拷贝和析构的次数那么从另一个角度来说也是编译器对程序执行效率上进行了优化。 所以在这里编译器偷偷帮我们做了一层优化 当我们这样去调用: Person p MyBusiness(); 编译器偷偷将我们的代码更改为 : void MyBussiness(Person _result){_result.X:X(); //调用 Person 默认拷贝构造函数//.....对_result 进行处理return;}
int main(){Person p; //这里只分配空间不初始化MyBussiness(p);
} 构造函数调用规则 默认情况下 c 编译器至少为我们写的类增加 3 个函数 1默认构造函数 ( 无参函数体为空 ) 2默认析构函数 ( 无参函数体为空 ) 3默认拷贝构造函数对类中非静态成员属性简单值拷贝 如果用户定义拷贝构造函数c 不会再提供任何默认构造函数 如果用户定义了普通构造( 非拷贝 ) c 不在提供默认无参构造但是会提供默认拷贝构造 深拷贝和浅拷贝 浅拷贝 同一类型的对象之间可以赋值使得两个对象的成员变量的值相同两个对象仍然 是独立的两个对象这种情况被称为浅拷贝. 一般情况下浅拷贝没有任何副作用但是当类中有指针并且指针指向动态分配 的内存空间析构函数做了动态内存释放的处理会导致内存问题。 深拷贝 当类中有指针并且此指针有动态分配空间析构函数做了释放处理往往需要自 定义拷贝构造函数自行给指针动态分配空间深拷贝。 class Person{
public:Person(char* name,int age){pName (char*)malloc(strlen(name) 1);strcpy(pName,name);mAge age;}//增加拷贝构造函数Person(const Person person){pName (char*)malloc(strlen(person.pName) 1);strcpy(pName, person.pName);mAge person.mAge;}~Person(){if (pName ! NULL){free(pName);}}
private:char* pName;int mAge;
};
void test(){Person p1(Edward,30);//用对象 p1 初始化对象 p2,调用 c提供的默认拷贝构造函数Person p2 p1;
} 多个对象构造和析构 初始化列表 构造函数和其他函数不同除了有名字参数列表函数体之外还有初始化列表。 初始化列表简单使用 : class Person{
public:#if 0//传统方式初始化Person(int a,int b,int c){mA a;mB b;mC c;}#endif//初始化列表方式初始化Person(int a, int b, int c):mA(a),mB(b),mC(c){}void PrintPerson(){cout mA: mA endl;cout mB: mB endl;cout mC: mC endl;}
private:int mA;int mB;int mC;
}; 注意初始化成员列表(参数列表)只能在构造函数使用。 类对象作为成员 在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象 叫做对象成员。 C中对对象的初始化是非常重要的操作当创建一个对象的时候 c编译器必 须确保调用了所有子对象的构造函数。如果所有的子对象有默认构造函数编译器可以自动调用他们。但是如果子对象没有默认的构造函数或者想指定调用某个构造函数怎么办 那么是否可以在类的构造函数直接调用子类的属性完成初始化呢但是如果子类的成员属性是私有的我们是没有办法访问并完成初始化的。 解决办法非常简单对于子类调用构造函数c 为此提供了专门的语法即构造 函数初始化列表。 当调用构造函数时首先按各对象成员在类定义中的顺序和参数列表的顺序无关 依次调用它们的构造函数对这些对象初始化最后再调用本身的函数体。也就是 说先调用对象成员的构造函数再调用本身的构造函数。 析构函数和构造函数调用顺序相反先构造后析构 //汽车类
class Car{
public:Car(){cout Car 默认构造函数! endl;mName 大众汽车;}Car(string name){cout Car 带参数构造函数! endl;mName name;}~Car(){cout Car 析构函数! endl;}
public:string mName;
};
//拖拉机
class Tractor{
public:Tractor(){cout Tractor 默认构造函数! endl;mName 爬土坡专用拖拉机;}Tractor(string name){cout Tractor 带参数构造函数! endl;mName name;}~Tractor(){cout Tractor 析构函数! endl;}
public:string mName;
};
//人类
class Person{
public:#if 1//类 mCar 不存在合适的构造函数Person(string name){mName name;}#else//初始化列表可以指定调用构造函数Person(string carName, string tracName, string name) : mTractor(tracName), mCar(carName), mName(name){cout Person 构造函数! endl;}#endifvoid GoWorkByCar(){cout mName 开着 mCar.mName 去上班! endl;}void GoWorkByTractor(){cout mName 开着 mTractor.mName 去上班! endl;}~Person(){cout Person 析构函数! endl;}
private:string mName;Car mCar;Tractor mTractor;
};
void test(){//Person person(宝马, 东风拖拉机, 赵四);Person person(刘能);person.GoWorkByCar();person.GoWorkByTractor();
} explicit 关键字 c提供了关键字 explicit 禁止通过构造函数进行的隐式转换。声明为 explicit 的构造函数不能在隐式转换中使用。 [explicit 注意 ] explicit 用于修饰构造函数 , 防止隐式转化。 是针对单参数的构造函数( 或者除了第一个参数外其余参数都有默认值的多参构造 ) 而言。 class MyString{
public:explicit MyString(int n){cout MyString(int n)! endl;}MyString(const char* str){cout MyString(const char* str) endl;}
};
int main(){//给字符串赋值还是初始化//MyString str1 1; MyString str2(10);//寓意非常明确给字符串赋值MyString str3 abcd;MyString str4(abcd);return EXIT_SUCCESS;
} 动态对象创建 当我们创建数组的时候总是需要提前预定数组的长度然后编译器分配预定长度的数组空间在使用数组的时会有这样的问题数组也许空间太大了浪费空间也许空间不足所以对于数组来讲如果能根据需要来分配空间大小再好不过。 所以动态的意思意味着不确定性。 为了解决这个普遍的编程问题在运行中可以创建和销毁对象是最基本的要求。当然 c 早就提供了动态内存分配 dynamic memory allocation , 函数 malloc 和 free 可以在运行时从堆中分配存储单元。 然而这些函数在 c 中不能很好的运行因为它不能帮我们完成对象的初始化工作。 对象创建 当创建一个 c 对象时会发生两件事 : 1. 为对象分配内存 2. 调用构造函数来初始化那块内存 第一步我们能保证实现需要我们确保第二步一定能发生。c 强迫我们这么 做是因为使用未初始化的对象是程序出错的一个重要原因。 C 动态分配内存方法 为了在运行时动态分配内存c 在他的标准库中提供了一些函数 ,malloc 以及 它的变种 calloc 和 realloc, 释放内存的 free, 这些函数是有效的、但是原始的 需要程序员理解和小心使用。为了使用 c 的动态内存分配函数在堆上创建一个 类的实例我们必须这样做 class Person{
public:Person(){mAge 20;pName (char*)malloc(strlen(john)1);strcpy(pName, john);}void Init(){mAge 20;pName (char*)malloc(strlen(john)1);strcpy(pName, john);}void Clean(){if (pName ! NULL){free(pName);}}
public:int mAge;char* pName;
};
int main(){//分配内存Person* person (Person*)malloc(sizeof(Person));if(person NULL){return 0;}//调用初始化函数person-Init();//清理对象person-Clean();//释放 person 对象free(person);return EXIT_SUCCESS;
} 问题 1) 程序员必须确定对象的长度。 2) malloc 返回一个 void 指针 c 不允许将 void 赋值给其他任何指针必须强转。 3) malloc 可能申请内存失败所以必须判断返回值来确保内存分配成功。 4) 用户在使用对象之前必须记住对他初始化构造函数不能显示调用初始化 ( 构造 函数是由编译器调用) 用户有可能忘记调用初始化函数。 c 的动态内存分配函数太复杂容易令人混淆是不可接受的 c 中我们推荐使 用运算符 new 和 delete new operator C中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为 new 的运算符里。当用 new 创建一个对象时它就在堆里为对象分配内存并调用 构造函数完成初始化。 Person* person new Person; 相当于 : Person* person (Person*)malloc( sizeof (Person)); if (person NULL){ return 0 ; } person-Init(); 构造函数 New 操作符能确定在调用构造函数初始化之前内存分配是成功的所有不用显式 确定调用是否成功。 现在我们发现在堆里创建对象的过程变得简单了只需要一个简单的表达式它带 有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里创建对 象一样简单。 delete operator new 表达式的反面是 delete 表达式。 delete 表达式先调用析构函数然后释放内 存。正如 new 表达式返回一个指向对象的指针一样 delete 需要一个对象的地址。 delete 只适用于由 new 创建的对象。 如果使用一个由 malloc 或者 calloc 或者 realloc 创建的对象使用 delete, 这个行为是未定义的。因为大多数 new 和 delete 的实现机制都使用了 malloc 和 free, 所以很 可能没有调用析构函数就释放了内存。 如果正在删除的对象的指针是 NULL, 将不发生任何事因此建议在删除指针后立 即把指针赋值为 NULL 以免对它删除两次对一些对象删除两次可能会产生某些 问题。 class Person{
public:Person(){cout 无参构造函数! endl;pName (char*)malloc(strlen(undefined) 1);strcpy(pName, undefined);mAge 0;}Person(char* name, int age){cout 有参构造函数! endl;pName (char*)malloc(strlen(name) 1);strcpy(pName, name);mAge age;}void ShowPerson(){cout Name: pName Age: mAge endl;}~Person(){cout 析构函数! endl;if (pName ! NULL){delete pName;pName NULL;}}
public:char* pName;int mAge;
};
void test(){Person* person1 new Person;Person* person2 new Person(John,33);person1-ShowPerson();person2-ShowPerson();delete person1;delete person2;
} 用于数组的 new 和 delete 使用 new 和 delete 在堆上创建数组非常容易。
//创建字符数组
char* pStr new char[100];
//创建整型数组
int* pArr1 new int[100];
//创建整型数组并初始化
int* pArr2 new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//释放数组内存
delete[] pStr;
delete[] pArr1;
delete[] pArr2; 当创建一个对象数组的时候必须对数组中的每一个对象调用构造函数除了在栈上可以聚合初始化必须提供一个默认的构造函数。 class Person{
public:Person(){pName (char*)malloc(strlen(undefined) 1);strcpy(pName, undefined);mAge 0;}Person(char* name, int age){pName (char*)malloc(sizeof(name));strcpy(pName, name);mAge age;}~Person(){if (pName ! NULL){delete pName;}
}
public:char* pName;int mAge;
};
void test(){//栈聚合初始化Person person[] { Person(john, 20), Person(Smith, 22) };cout person[1].pName endl;//创建堆上对象数组必须提供构造函数Person* workers new Person[20];
} delete void*可能会出错 如果对一个 void* 指针执行 delete 操作这将可能成为一个程序错误除非指针指 向的内容是非常简单的因为它将不执行析构函数. 以下代码未调用析构函数导致可用内存减少。 class Person{
public:Person(char* name, int age){pName (char*)malloc(sizeof(name));strcpy(pName,name);mAge age;}~Person(){if (pName ! NULL){delete pName;}
}
public:char* pName;int mAge;
};
void test(){void* person new Person(john,20);delete person;
} 问题malloc 、 free 和 new 、 delete 可以混搭使用吗也就是说 malloc 分配的内 存可以调用 delete 吗通过 new 创建的对象可以调用 free 来释放吗 使用 new 和 delete 采用相同形式 Person* person new Person[ 10 ]; delete person; 以上代码有什么问题吗(vs 下直接中断、 qt 下析构函数调用一次 ) 使用了 new 也搭配使用了 delete 问题在于 Person 有 10 个对象那么其他 9 个 对象可能没有调用析构函数也就是说其他 9 个对象可能删除不完全因为它们 的析构函数没有被调用。 我们现在清楚使用 new 的时候发生了两件事 : 一、分配内存二、调用构造函数 那么调用 delete 的时候也有两件事一、析构函数二、释放内存。 那么刚才我们那段代码最大的问题在于person 指针指向的内存中到底有多少个 对象因为这个决定应该有多少个析构函数应该被调用。换句话说person 指针 指向的是一个单一的对象还是一个数组对象由于单一对象和数组对象的内存布局 是不同的。更明确的说数组所用的内存通常还包括“ 数组大小记录 ” 使得 delete 的时候知道应该调用几次析构函数。单一对象的话就没有这个记录。单一对象和数 组对象的内存布局可理解为下图: 本图只是为了说明编译器不一定如此实现但是很多编译器是这样做的。 当我们使用一个 delete 的时候我们必须让 delete 知道指针指向的内存空间中是 否存在一个“ 数组大小记录 ” 的办法就是我们告诉它。当我们使用 delete[] 那么 delete 就知道是一个对象数组从而清楚应该调用几次析构函数。 结论 : 如果在 new 表达式中使用 [] 必须在相应的 delete 表达式中也使用 []. 如果在 new 表达式中不使用[], 一定不要在相应的 delete 表达式中使用 []