1、数组是自动分配空间,指针要手工分配空间(int *p = new int;)
2、在Unix上,程序出现段错误的时候,系统会生成core 文件,会把出现错误的那一刻的程序镜像保存在此文件中
3、结构的成员变量出现数组:
struct Account{
long id ;
//char name[ 50 ] ;
//char password[ 10 ] ;
char * pname ;
char * ppwd ;
double balance ;
};
数组长度100-200字节,最好用数组做,这样方便
数组很大的时候,可以考虑用指针,赋值的时候要注意:
a.pname = new char[50];
strcpy(a.pname , "huxinzhe");
在结构声明时,不能给成员变量赋值
结构声明只是声明了一种数据类型,还没有为这个结构申请空间,赋值的数据没有空间保存
4、危险指针的用法
使用NULL指针的内容 ---隐蔽性不高,容易发现
使用没有初始化的指针 --- 会导致无意当中修改其它变量,破坏性很大,避免的办法 int *p = NULL;
使用已经被delete的指针内容 --- 修改其它变量 避免的办法 delete p ; p=NULL;
返回一个局部变量的地址
由函数申请空间,由调用者释放
#include <iostream>
using namespace std;
char * readLine(){
char * line = new char[ 100 ] ; //在函数中申请了空间,但是没有地方释放
strcpy( line , "hello world" ) ;
cout<<"line = " << line << endl;
return line ; //返回变量的指针,若是局部变量,在函数返回的时候,就消失了,若在堆中申请空间,没有释放空间的机会
}
int main(){
char * p = readLine();
cout<<"return from readLine "<<endl;
cout<<"p = " << p << endl;
delete p; //释放别人申请的空间,不安全
return 0 ;
}
解决的办法 : 在函数外部申请一个指针并初始化,作为参数传给函数,这样可以保存数据并返回
在参数中传数组的起始地址,和数组长度
只要是参数传指针,指针在传给函数之前要进行初始化
5、编译的时候不想看到警告信息 g++ -w
6、函数指针的声明:
函数的名字就是这个函数的首地址
函数指针之间的赋值,也要同类型的
函数指针是不需要释放的。
函数指针,设计通用算法
7、多层指针
int i = 10;
int * p = &i;
int ** pp =&p;
int**(*ppp) = & pp;
*p = i ;
*pp = p;
**pp = i;
多层指针的使用 :
int ia[2][3] = {{11,12,13},
{21,22,23}}; //声明并定义一个二维数组
cout << ia[0] << endl; //打印的是ia的第一个数组的首地址
int * p = ia[0]; //保存ia第一个数组的首地址
cout << a[0][2] <<endl;
cout << p[2] <<endl; //与上面一行具有相同的效果 。
ia[0]的类型就是int*类型的,指向ia的第一个数组
ia也是个指针类型,指向那个二维数组 ,是 int**
二维数组是一个二级指针
二维数组的每个元素是一级指针
int (*pp)[3] = ia; //行指针,指向二维数组的一行,[]中的数字表示每一行的元素个数
cout << pp[1][1] <<endl; //通过二级指针去数组元素 pp[1][1] <=> *( *(pp+1) +1 )
行指针可以描述指向数组的元素个数,以行为单位,加1就是指针指到下一行
打印数组在内存中地址
int (*p1)[3] --> p1的类型是int[3] p1+1,走3个int,一个数组的长度
int ** p2 --> p2的类型是int* p2+1,走1个int
8、char* names[100]; 声明一个数组,每个元素类型是char*
chat* names[3] = {"liucy","huxz","tangliang"}; //这样的声明更清晰的说明是char的二维数组
for(int i = 0 ; i < 3 ; i++){
cout << names[i] <<endl; //打印每个元素
}
9、void*
任何类型的变量的地址都可以把地址存进来
通用指针的存储,不能做任何运算,是纯粹的存储地址而用
10、const与指针
const int *p 常量指针 不能通过指针改变变量的值 const离int近,值不能改
int* const p 指针常量 指针不能改,指向的对象是固定的,但可以通过指针改变变量的值
11、指针的要求
指针的声明和基本运算
数组与指针的关系,结构和指针的关系,字符指针的用法
堆空间及危险用法
12、引用
引用就是一个变量的别名 int &iR= i; //给i起的别名iR
引用声明的时候必须初始化(与一个变量保持关联关系),一旦赋值,就不能把别名再给别的变量了
int iL = 100;
iR = iL; // 相当于把i的值改变了,iR还是i的别名
13、引用的使用
以引用的形式传参数 fn(int &a)
在函数内部的操作就是对此实参进行操作
一般情况下 (1)在真的想改变实参的值的时候
(2)在实参的大小比较大的时候,传引用,这样系统不生成临时变量,减小耗费内存
的时候传引用。
fn(const int &a) 这样传引用,不会创建临时变量,也不会改变实参的值
14、周末项目
(1)完善项目 把密码用char数组保存
(2)写一个函数,对所有类型的数组进行排序
提示:通过函数指针实现对struct传参数
void* 指针,存储所有类型指针
void sort (int perlen , int *p , int len , void (*order)(int * ,int *)){
char *p1 = (char*)p; //char是基本类型里最小的,可以模拟步长
for(int i = 0 ; i < len ; i ++{
for(int j = 0 ; i < len ; i++){
order(p+i*prolen,p+j*prolen);
}
}
)
1、排序函数
void order(int* p1, int* p2){
if(*p1 > *p2){
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
}
void sort (int *p , int len , int perLen , void (*pOrder)(void* , void*)){
char* pt = (char*)p;
for(int i = 0 ; i < len ; i++){
for(int j = i ; j < len ; i ++){
pOrder(pt+i*perLen , pt+j*perLen);
}
}
}
(1)将输入参数int* -> void* (void* 可以存储任何类型的地址,可以通用 )
(2)表示数组的第i个元素,事先要知道数组每个元素的大小(参数传入)
i*perLen => 当不知道指针类型时,表示走一步,应越过的字节数
p+i*perLen => 表示第i个元素的地址
(3)具体的数组要有具体的排序方法
调用函数,当不知道函数名时,可以通过函数指针调用
==================================================
sort.h
==================================================
void sort( void * p , int len , int perLen ,
void (*pOrder)(void * , void * ) );
==================================================
sort.cc
==================================================
void sort( void * p , int len , int perLen ,
void (*pOrder)(void * , void * ) ){
char* pt = (char*)p ;
for( int i = 0 ; i < len ; i++ ){
for( int j = i ; j < len ; j++ ){
pOrder(pt+i*perLen,pt+j*perLen ) ;
}
}
}
==================================================
main.cc
==================================================
#include <iostream>
#include "sort.h"
using namespace std;
void orderInt( void * p1 , void * p2 ){
int * t1 = (int*)p1 ;
int * t2 = (int*)p2 ;
if( *t1 > *t2 ){
int temp = *t1 ;
*t1 = *t2 ;
*t2 = temp ;
}
}
struct Person{
char name[ 40 ] ;
int age ;
int id ;
} ;
void orderByAge( void * p1 , void* p2 ){
Person * t1 = (Person*)p1 ;
Person * t2 = (Person*)p2 ;
if( t1->age > t2->age ){
Person t = *t1 ;
*t1 = *t2 ;
*t2 = t ;
}
}
void orderById( void *p1 , void* p2 ){
Person* t1 = (Person*)p1 ;
Person* t2 = (Person*)p2 ;
if( t1->id > t2->id ){
Person t = *t1 ;
*t1 = *t2 ;
*t2 = t ;
}
}
void orderByName( void * p1 , void* p2 ){
Person* t1 = (Person*)p1 ;
Person* t2 = (Person*)p2 ;
if( strcmp( t1->name , t2->name ) > 0 ){
Person t = *t1 ;
*t1 = *t2 ;
*t2 = t ;
}
}
int main(){
int ia[] = { 3,1,6,3,6,8,3,468,89 };
sort( ia , 9, sizeof(int), orderInt );
for( int i = 0 ; i < 9 ; i++ ){
cout<<ia[i] <<" " ;
}
cout<<endl;
Person pers[ 3 ] ;
pers[0].id = 1 ;
pers[0].age = 29 ;
strcpy( pers[0].name , "liucy" ) ;
pers[1].id = 2 ;
pers[1].age = 28 ;
strcpy( pers[1].name , "huxinzhe" ) ;
pers[2].id = 3 ;
pers[2].age = 26 ;
strcpy( pers[2].name , "xuehailu" ) ;
sort( pers , 3 , sizeof(Person), orderByAge );
for( int i = 0 ; i < 3 ; i++ ){
cout<<pers[i].name <<","<<pers[i].age<<",";
cout<<pers[i].id<<endl ;
}
sort( pers, 3, sizeof( Person) , orderById ) ;
for( int i = 0 ; i < 3 ; i++ ){
cout<<pers[i].name <<","<<pers[i].age<<",";
cout<<pers[i].id<<endl ;
}
sort( pers , 3 , sizeof( Person ) , orderByName );
for( int i = 0 ; i < 3 ; i++ ){
cout<<pers[i].name <<","<<pers[i].age<<",";
cout<<pers[i].id <<endl;
}
return 0 ;
}
2、面向对象
封装:对象表示
继承:更好的代码重用
多态
对象的组成 : 属性 成员变量
行为 函数
面向过程的表示方法:
数据与函数分离,关系松散
封装的作用,把数据和函数封装到一起,保证数据专用
全局函数:在类外面的函数,要使用成员变量,要通过参数传进来
成员函数:在类内,可直接使用自己类的成员变量
对于类的变量的初始化:
Person p ;
strcpy(p.name , "liucy");
p.age = 23;
p.speak();
对成员变量和成员函数的使用都要通过类的对象
public 关键字,表示在其他地方可以使用
默认是私有的,在main函数中不能使用
成员变量和成员函数依赖于类的对象(实例)
类型是对对象的描述
对象是类型的实例
对象自己的成员函数访问自己的成员变量
什么是类?类由什么组成?
怎么使用类?及类和对象的关系?
成员变量和成员函数归谁所有?
面向对象的方法写程序
(1)首先写一个类,描述对象
用变量表示属性,函数表示行为
(2)调用函数
创建一个类的对象,通过对象调用函数
1、类型封装
类 --> 对象
描述 : (1)属性
(2)行为 ---属性和行为是属于类的
创建对象。
2、构造函数 --- 初始化对象
(1)构造函数名字必须与类名一样
(2)构造函数不能写返回类型
构造函数在创建对象时,系统自动调用
构造函数允许重载,按用户要求,适应多种情况
当类中一个构造函数都没有的时候,系统提供默认无参的构造函数
但如果在类中定义了一个构造函数,系统就不提供默认的了,所以,建议,在写构造函数时,都要写一个无参的构造函数
3、对类的安全的定义
变量 -> 私有 -> private 保护变量,防止外界随意修改,只能在类的内部使用(只能被自己的成员函数使用)
函数 -> 公有 -> public
为保证私有变量也能为外界访问,在类内部提供set,get方法
set方法没有返回值,但要求有参数
get方法肯定有返回值,没有参数
4、定义类的步骤
(1)写属性的行为
成员变量 函数
(2)成员变量--> private
成员函数 --> public
(3)特殊函数
构造函数,建议写2个。一个有参数的,一个无参的
get,set函数,一个成员变量对应一对get,set函数,是外界访问此变量的唯一途径
对于setXXX函数,没有返回值,有参数,参数的类型与被赋值的属性类型一致。
对于getXXX函数,没有参数,有返回值,返回值的类型与输出的属性类型一致。
类的行为
5、封装
定义类的过程就是封装
练习:封装一个account类型
属性:id , password ,name ,balance
行为:save , withdraw , query
6、把account类拆成多文件结构
(1)方便快速浏览整个类定义
(2)使用方便,包含头文件即可
long Account::getId(){...........}
在函数实现时,函数前把类名加上,确定函数的所有权,避免2个类中有同名的函数,编译出错。
" :: "称为域访问控制符。
7、Person per ; 创建了一个对象,系统调用构造函数。
Person *p = NULL ; 声明一个类的指针,系统是不会调用构造函数的
Person * p = NULL;
p = new Person ; 在堆中申请一个类空间
delete p;
在堆中的数据没有名字,只能通过指针访问类对象,访问类的成员变量:
(*p).sayHello();
p->sayHello();
Person ps[5];声明一个Person类型的数组,会调用5次构造函数
数组在声明的时候,系统会为其分配空间
并且在声明数组的时候,没有机会指定构造函数,只会调用无参的构造函数
当一个类定义中没有无参的构造函数,但要声明一个类的数组时,可以声明一个指针数组
Person *ps[5]; ---声明指针的时候,类对象没有创建,数组中每个元素都是Person类型的指针,达到:
(1)不用构造函数
(2)实现使用对象
for(int i = 0 ; i <5 ; i++){
ps[i] = new Person(i , 20+i); //用循环初始化每个指针
}
for(int i = 0 ; i < 5 ; i++){
delete ps[i]; //释放指针指向的空间
ps[i] = NULL ;
}
8、课堂练习:
要求:main()函数中不能写代码,在运行程序时,打印“Hello World”
答案:全局变量的初始化操作在main函数执行之前就已经初始化了。因此,可以写一个全局变量的类,其无参构造函数写输出语句。
然后声明一个类的全局对象。
9、在构造函数中,当成员变量名称和形参名称相同,避免冲突的方法:
在每个对象内部都有一个名为this的指针,指向自己,使用自己的成员变量 this->name
10、析构函数
当对象的生命周期结束,系统要回收空间,会自动被调用
申请的所有资源(new char[20]),在析构函数中释放
若对象是在main函数中的变量,在main函数结束之后,才会调用析构函数
若对象是一个函数中的局部变量,在函数返回时,调用析构函数
析构函数调用 : 对象生命周期结束,系统自动调用析构函数
可以把释放资源的代码写在析构函数中
析构函数的写法:构造函数前加 “~”
不能重载,不能有参数
若不写析构函数,系统会提供默认的析构函数
当类中用到了系统空间:new内存,打开文件
就需要写析构函数,释放资源
11、作业:(1)利用面向对象的思想实现栈结构,并自己写main函数测试。
(2)把原来的银行系统改成面向对象的形式。