C++ 部分知识点
1,return语句返回函数的返回值,就是函数的类型,函数只能有唯一的返回值;
return可以停止函数,并将控制返回主调函数;
一个函数可以有许多return语句,执行到哪个return语句,哪个起作用;
如果函数的类型和return表达式中的语句不一致,则以函数的类型为准;
函数的类型觉得return返回值的类型,对数值型数据,可以进行类型转换;
2,函数原型声明:
如果调用的是自定义的函数,并且该函数和主函数在同一个程序中且位置在主函数之后,则必须在调用函数之前对函数进行声明;
void main(){
float a,b,c;
float max(float,float); //函数原型声明;
cin>>a>>b;
c=max(a,b);
cout<<"The max is"<<c<<endl;
}
float max(float x, float y){
float z;
z=(x>y)? x :y ;
return z;
}
3,函数可以嵌套调用,但不可以嵌套定义;
4,函数的递归调用:在调用函数的过程中,直接或间接的调用函数本身;
5,作用域和存储类:
作用域:即某个标识符在哪个区间有效;
作用域分为5类:块作用域,文件作用域,函数原型作用域,函数作用域和区间作用域;
块作用域:
块:用花括号括起来的程序为一个块;作用域在块内,始于标识符的声明处,结束于块的结尾处;
形参也属于局部变量;
主函数中定义的变量也属于局部变量,只在主函数内部有效;
不同的函数可以使用相同名字的局部变量,它们在内存中分属不同的空间,互不干扰;
定义变量就是在内存中开辟空间;
注意:具有块作用域的标识符在其作用域内,将屏蔽其作用块包含本块的同名标识符,即变量名相同,局部更优先;
文件作用域:函数外定义的变量称为全局变量,全局变量的作用域称为文件作用域,即在整个文件中都是可以访问的;其缺省的范围是从全局变量的定义处开始,到源程序的结束;
当块作用域内的变量和全局变量同名时,局部变量优先;
在同一个源文件中,外部变量和局部变量同名,则在局部变量的作用范围内,外部变量不起作用;
int a = 3, b =5;
int max(int a, int b){
int c ;
c = a>b?a:b;
return c;
}
void main(void){
int a =8; //覆盖了外部的全局变量a;
cout<<max(a,b)<<endl;
} //结果是输出 8 ;
在块作用域内,可通过作用域运算符::来引用与局部变量同名的全局变量;
int i = 100;
void main(void){
int i , j = 50;
i = 18 ; //访问局部变量i;
::i= ::i + 5 ; //访问全局变量i;
j = ::i + i ; //访问全局变量i和局部变量j;
cout<<"::i="<<::i<<'\n'; // \n的引号是单引号;
cout<<"j="<<j<<'\n';
}
3,函数原型作用域:
在函数原型的参数表中声明的标识符所具有的作用域称为函数原型作用域,从函数原型声明处开始,到函数原型声明结束处为止;
float tt( int x , float y); //函数tt的原型说明;
可以在函数原型声明中,只声明参数类型,省略参数名;
局部变量的分类:
静态局部变量:存储在静态区;
动态变量:默认,存储在动态区;
寄存器变量:在cpu内部存储;
4,内联函数:在函数声明前加inline关键字;(c++中,除函数内部有循环,switch分支语句及复杂的嵌套if语句外,任何函数都可以生命为内联函数,如被调函数)
内联函数的实质是采用空间(占用更多的空间)换时间(换取更少的执行时间);
5,具有缺省参数值和参数可变的函数:
在c++中定义函数时,允许给参数指定一个缺省值。在调用函数时,若明确给出了实参的值,则使用相应的实参的值,若没有给出相应的实参,则使用缺省值;
6,函数重载:是指完成不同功能的函数可以使用相同的函数名;
定义函数的重载,必须有不同的参数个数或不同的参数类型,这样编译系统才能根据不同的实参决定调用哪个函数;
仅仅函数的类型不同,不能定义为重载函数;
c++的编译器,是根据函数的实参来决定调用哪个函数的;
7,宏定义:#define 标识符 字符串 //使用指定的标识符代替字符串;
注意:末尾没有分号;
作用域为:声明处开始到程序文件结束为止,可以使用#undef 结束作用域;
在程序中用双引号括起来的字符串内容,即使与宏名相同,也不进行置换;
在进行宏定义中,可以使用已定义的宏名,来进行层层置换;
#define G 9.8
# undef G
8,文件包含:#include “文件名”
7,数组:
c++中不允许对数组进行动态定义,即数组的大小不能是变量,必须是常量;
数组的初始化:
int a[10]= {0,1,2,3,4,5,6,7,8,9}
int a[10] = {0,1,2,3} //未赋值的那部分是0;
不能给数组整体赋值:
如 int a[10]={0,1,2,3......,9} //是非法的;
数组在内存中按顺序存放,第一个元素位于最底端;
8,冒泡法对数组中的n个数进行排序:
首先将相邻的两个数进行比较,然后调换位置,小的放到前边,n个数要比较n-1趟;
每一趟要比较的次数是不同的:第一趟比较n-1次,第二趟比较n-2次,第三趟比较n-3次,,,,第n-1趟比较1次(n-(n-1));
for(int j=1;j<=n-1;j++) { //控制比较趟数
for(int i=1;i<=n-j;i++){ //控制每趟比较的次数
if(a[i]>a[i+1]){
t=a[i];
a[i]=a[i+1];
a[i+1]=t;
}
}
}
一般元素的序号是从0开始,因此程序可以变动如下:
for(int j=0;j<n-1;j++){
for(int i=0;i<n-1-j;i++){
//。。。。。
}
}
9,数组中分行或全部赋值时,第一维可以省,第二维不可以省。
int a[][4]={{1,2},{5,6,7,8},{9,10,11,12}}
10,c++中,数组名默认是数组在内存中存放的首地址;
用数组名做函数参数,实参和形参都应该是数组名,此时函数传递的是数组在内存中的地址;实参中地址传到实参中,实参形参共用同一段内存,形参数组中的值发生变化,实参数组中的值夜发生变化;
用数组名做函数参数,应该在主调函数和被调函数中国分别定义数组,并且类型相同;
实参数组的大小需指定,形参数组的大小不用指定;数组名做实参,实际上是传递数组的首地址;
11,字符数组:用来存放字符数据的数组称为字符数组;一个元素放一个字符;
定义:char 数组名[常量表达式]
char c[10] = {'I','','a','m','','a','','b','o','y'};
如果省略数组长度,则字符数即为数组长度;char c[] = {'I','','a','m','','a','','b','o','y'};
取相应的ASCII码值;
char str[] = {66,67,68}; //'A','B','D'
字符串和字符数组的区别:
如果不指定长度,字符数组的长度就是字符的个数,字符串的长度则是字符的个数加末尾 \0,就是字符的个数+1;
'\0'的ASCII码值是0,而空格的ASCII码值是32;
char str[12]=[];
str = "HELLO" //非法赋值,因为str是字符数组在内存中的首地址,一经定义,便成为常量,不可再赋值;
而应该这样定义:char str[] = "HEELO";
注意:两字符串间不能直接进行比较,需要通过字符串函数来执行;
11,指针的概念:
数据在内存中是如何存取的?
系统根据程序中定义的变量的类型,给变量分 配一定的长度空间。字符型占1个字节,整型占4个字节,,,内存区的每个字节都有编号,称之为地址;
1,直接访问:按变量地址存取变量的值。cin>>i; 实际是放到定义i单元的地址中;
2,间接访问:将变量的地址存放到另一个单元P中,通过p取出变量地址,再针对变量进行操作;
一个变量的地址称为该变量的指针;
12,变量的指针和指向变量的指针变量:
变量的指针就是变量的地址,当变量定义后,其指针就是一个常量;
int i ; &i =2000H ; //2000H就是指针即地址;
指向指针的变量:定义的一个变量用来存放另一个变量的地址;在编译时,同样分配一定字节的存储单元,未赋初值时,存储单元内的字节是随机的;
指针变量的定义:类型标识符 *变量名; int *i_point;
指针变量也可以赋值:
int i, *i_point;
i_point = &i; //或者是定义指针变量时赋初值,int *i_point = &i;
一个指针变量只能指向同一类型的变量;即整型指针变量只能放整型数据的地址,而不能放其他类型数据的地址;
* 在定义语句中只表示变量的类型是指针,*在语句中表示指向,&表示地址;
int *i_point = &i; //此处的*表示类型
*i_point = 3; //此处的*表示指向;
指针变量的引用:指针变量只能存放地址,不要将非地址数据赋给指针变量;
int *p,i;
p = &i ; //不能是p= 100;
引用:
*p = 100; //*p代表引用,通过指针给变量赋值;
cout<<*p<<endl; //此处*p代表引用,*是指向;但是指针变量未赋值(即未声明p=&i);
注意:指针变量未赋值时(p=&i)不能作指向运算(*p=100)
8,++,-- ,* 优先级相同,都是右结合;
9,指针变量作为函数参数:
函数的参数可以是指针类型,作用是将变量的地址传给另一个函数;
指针作为函数参数和变量作为函数参数不同:变量作为参数是将具体值传给函数,指针作为参数是将内存地址传给参数;
函数调用不能改变实参指针变量的值,但可以改变实参指针变量所指向变量的值;
10,数组的指针和指向数组的指针变量:
数组名就是数组的起始地址,数组的指针就是数组的起始地址;数组元素的指针就是数组元素的地址;
一:指向数组元素的指针变量的定义和赋值:
int a[10],*p;
p = &a[0]; //数组第一个元素地址;
p = a ; //直接用数组名赋值;
若数组元素是int型,则指向其的指针变量也应该定义为int型;
int a[10];
int *p = a ;
int *p = &a[0]; //这两种情况均为赋初值;
二:通过指针引用数组元素:
int a[10];
int *p = a ;
*p = 1; //相当于a[0] = 1;
*(p+1) = 2; //相当于p[1]=2; *(++p)=2,p=p+1, *p=2,
p+i或a+i均表示p[i]或a[i]的地址 &p[i]或&a[i], 即 *[a+i] = a[i] 或*[p+i] = p[i] ;
用指向数组的指针变量输出数组中元素:
void main(void){
int a[10],i;
int *p;
for(int i =0 ;i<10;i++)
cin>>a[i];
for(p =a ;p<a+10;p++)
cout<<*p<<'\t';
}
void main(void){
int a[10],i;
int *p=a;
for(i = 0;i<10;i++)
cin>>a[i]>>'endl';
for(i=0;i<10;i++)
cout<<*p++<<endl;
} //*p, p+1,输出数据后指针+1;
三:数组名作函数参数:
数组名可以作函数的形参实参,传递的是数组的地址;在函数调用时,形参数组并没有开辟新的内存,而是以实参数组的首地址作为形参数组的首地址;这样形参数组的元素值发生变化也就导致实参数组的元素值变化了;
1,形参实参都能用数组名:
形参数组必须进行类型说明;
void main(void){
int array[10];
。。。。
f(array,10);//实参数组
。。。
}
int f(int array[] , int n){ //形参数组必须进行类型说明,用数组名作形参,因为接收的是地址,可以不指定具体元素个数
。。。
}
2,实参用数组名,形参用指针变量:
void main(void){
。。。
f(a,10); //a是实参数组
。。。
}
int f(int *x , int n){//*x是形参指针;
。。。
}
3,实参形参都用指针变量:
void main(void){
int a[10],*p;
p=a;
。。。
f(p,10);// p是实参指针,实参指针调用前必须赋值,
}
int f(int *x,int n){//*x是形参指针
。。。
}
4,实参为指针变量,形参为数组名:
void main(void){
int a[10],*p;
p=a;
f(p , 10);
}
int f(int x[] , int n){
。。。。
}
4,多维数组:
a[1]+2 :是&a[1][2],a[1][2] = *(a[1]+2) ,即a[i][j] = *(a[i]+j);
a为二维数组名:a+1为a[1]的地址,也就是第一行的地址,所以为行指针;
a[1]为一位数组名,a[1]+1为a[1][1]的地址,就是第一行第一列的地址,所以a[1]为列指针;
a[1] = *(a+1); *(a+1)+2 = & a[1][2];
*(*(a+1)+2) = a[1][2] ;
**(a+1)=*(a[1])=*(*(a+1)+0)=a[1][0] ;
(*(a+1))[1] = *(*(a+1)+1)=a[1][1];
*(a+1)[1]=*((a+1)[1])=*(*((a+1)+1))=**(a+2)=a[2][0]
注意二维数组的各种表示法,a为常量
10,面向对象设计:
当为多余一个的成员函数或成员变量添加权限修饰符时,如public,可以使用冒号的形式:
class A{
float x , y;
public:
void doGet(float a ,float b){
//。。。
}
void doPost(void c){
// 。。。
}
}
成员函数和成员数据的定义不分先后,可以在类体内定义函数体,也可以在先说明函数原型,再在类体外定义函数体;
class A
float x , y;
public:
void doGet(float a ,float b);
void doPost(void c);
}
void A::doGet(float a, float b) {
//。。。
}
void A::doPost(void c){
//。。。
}
在类体外定义成员函数 的格式:
函数类型 类类型::函数名(参数列表){
函数体
};
在定义一个类时,要注意几点:
1,类有封装性,只是定义一个结构,其内的成员不能被声明为extern,register,auto来限定存储类型;
2,在定义类时只是定义一种导出的数据类型,并不为类分配存储空间,所以在定义类中的成员数据时,不能对其初始化;
class A{
int x = 2,y = 5; //不允许
extern flaot a; //不允许
}
c++中,结构体类型是类的一个特例,结构体和类的唯一区别:结构体类型中其成员的缺省的存取权限是公有的,而类中,其成员的缺省的存取权限是私有的;
类的变量称为对象;
对象的定义方法同结构体定义变量的方法一样,分为三种:当类中有数据成员的访问权限为私有时,不允许对对象进行初始化;
全局对象:在类后面声明的对象;class A{}a1,a2; //a1, a2 就是全局对象
局部对象:在函数里面声明的对象;void main(void){ A a3,a4; } //a3, a4就是局部对象
用对象直接访问成员变量时,通过成员选择运算符“”.“”只能访问公有的成员变量,要想访问私有成员变量,必须通过对象调用公有成员函数来获取;
a1.m=10;//对公有成员数据赋值;
a1.setValue(2,3); //对私有成员数据赋值;
同类型的对象之间可以整体赋值,这种赋值与对象的访问权限无关;
class A{
float x , y;
public :
float m , n;
void set(float a, float b){}
}
void main(void){
A a1, a2;
a1.m = 10;
a1.n = 5;
a1.setValue(7,8);
a2 =a1; //整体赋值;
}
11,类作用域:
类体的区域称为类作用域,类的成员函数和成员数据都是在这个范围内的,所以不能在主调函数内直接通过函数名和成员名来调用他们;而要通过对象来调用;
类类型的作用域:在函数定义之外声明的类,其类名的作用域是文件作用域;在函数体内声明的类,其类名的作用域是块作用域;
类时可以嵌套的,即在一个类的内部有定义一个类;
class A{
class B{
int a ,b;
public:
void setValue(int i , int j){
a=i;b=j;
}
};
float x ,y ;
public:
B b1,b2; //嵌套类的对象;在类A的定义中并不为b1,b2分配空间,而是在定义类A的对象时,才为嵌套类的对象b1,b2分陪空间;
void print(void){ .....}
}
12,类的对象如何引用私有数据成员:
1,通过公有函数为私有成员赋值:
class A{
float i ,j;
public :
void setValue(float a, float b){ i =a ; j = b;}
};
void main(void){
A a1;
a1.setValue(3,5);
}
2,通过指针访问私有数据成员:
class Test{
int x , y ;
public;
void setValue(int a, int b){ x = a ;y = b;}
void getValue(int *px ,int *py){ *px = x ; *py = y;}
}
void main(void){
Test t1,t2;
t1.setValue(3,5);
int a ,b;
t1.getValue(&a , &b);
cout<<a<<'\t'<<b<<endl; //输出3和5
}
3,利用函数访问私有成员的值:
class A{
int x, y;
public:
void setValue(int a ,int b){ x = a ;y = b;}
int getX(void){ return x ;}
int getY(void){return y;}
}
void main(void){
A a1,a2;
a1.setValue(3,5);
a = a1.getX(); // 将x赋给a
b = a1.getY(); //将y赋给b;
cout<<a<<'\t'<<b<<endl;
}
4,利用引用访问私有数据成员:
class Test{
int x , y;
public:
void setValue(int a , int b){ x = a ; y = b;}
void getValue(int &px ,int &py){ px = x ; px= y;}
}
void main(void){
Test t1 , t2;
t1.setValue(3 , 5);
int a , b;
t1.getValue(a , b);
cout<<a<<'\t'<<b<<endl; //输出 3 和5
}
12,定义类的指针及如何用指针来引用对象:
class A{{
flaot x ,y ;
public;
float sum(void){return x+y;}
void setValue(float a ,float b){ x = a ; y = b ;}
void print(void){}
}
void main(void){
A a1 ,a2;
A *p; //定义类指针
p = &a1; //给指针赋值;
p->setValue(2.0,3.0); //通过指针引用对象的成员函数;
p->print();
cout<<p->sum()<<endl;
a2.setValue(5.0,3.0);
a2.print();
}
13,构造函数和析构函数是在类体中声明的两种特殊函数:
构造函数:是创建对象时,对对象进行初始化;
析构函数:是系统释放对象前,对对象进行善后工作;
构造函数可以带参,可以重载, 同时没有返回值;
构造函数定义格式:
类名::类名(参数列表){ //函数体}
构造函数是类的成员函数,系统约定构造函数名必须与类名相同, 提供了一种简单的初始化对象的方法;
class A{
int x ,y;
A( int a ,int b){ x = a; y = b;} // 构造函数,初始化对象;
void set(int a , int b){x = a ; y = b;}
int sum(void){ return x+y ;}
void print(void){cout<<'x:'<<x<<'\t'<<'y:'<<y<<endl;}
}
void main(void){
A a1(10,20); //初始化对象
A a2(5,6);
a2.set(7,8); //利用成员函数重新为对象赋值
a1.print();
a2.print();
}
构造函数的几点注意:
1)构造函数名必须与类名相同;
2)定义构造函数时,不能指定返回值类型,也不用指定void类型;
3)一个类可以定义多个构造函数,但要满足重载(参数列表 不同)原则;
4)构造函数可以指定参数的缺省值;class A { int x ,y ; A(float a ,float b = 10){ x = a ;y = b;} } //不带缺省参数值的构造函数 A(){ x = 0 ; y = 0; }
5)若利用构造函数声明对象时,需要将构造函数声明为public;若定义的类只用于派生其他类,则应该将构造函数声明为protected;
6)由于构造函数是类的成员函数,所以它可以对公有成员,保护成员及私有成员均可以进行初始化;
每个对象都要有相应的构造函数,若没有显示定义的构造函数,系统默认为缺省的构造函数;
只允许这样定义对象: A a1 ,a2 ;
14,对于局部对象,全局对象,静态对象的初始化:
局部对象:只要创建对象,就要调用构造函数进行初始化;在函数内声明的对象就是局部对象;void main(void){ A a3(3 , 7)} //a3就是局部对象
静态对象:只在首次创建对象的时候调用构造函数初始化,并且由于其一直存在,所以只调用构造函数初始化一次;前边加上static的就是静态对象;
全局对象:是在main函数执行之前调用构造函数的;在函数外声明的对象就是全局对象;A a1(5);void main(void){ // 。。。} //a1就是全局对象 ,在main函数执行之前
15,缺省的构造函数 的格式:
类名::类名(){}
缺省的构造函数并不对所产生的对象的数据成员赋初值,即新产生的对象的数据成员的值是不确定的;
创建对象时,若类中显示定义了构造函数,则使用缺省的构造函数创建对象会报错:
class A{
int x ,y ;
public:
A(int a , int b){ x = a; y = b;}
}
void main(void){
A a1; //会报错;因为没有构造函数可供调用;
A a2( 3, 5);
}
注意:若类中定义了无参的构造函数或者是各参数都有缺省值的构造函数,则他们都是缺省的构造函数;缺省的构造函数只能有一个;任一对象的构造函数必须唯一;
如果在类中同时定义了无参的构造函数和各参数都有缺省值的构造函数,则在主函数中创建无参对象时,该对象两个构造函数都可以调用;
class A{
int x ,y ;
public:
A(){}
A(int a = 10 ,int b = 5){ x = a ; y = b; }
void print(void){ cout << 'x:'<<x<<'\t'<<'y:'<<y<<endl;}
}
void main(void){
A a1;//该对象两个构造函数都可以调用;
A a2(5 ,6);
}
15,析构函数:
定义格式:类名::~类名(){ //函数体 } ;
若类中没有显示定义析构函数,则系统会调用默认的析构函数,如果要释放new运算符的空间,则必须显示定义析构函数;
1)析构函数是成员函数,类体可写在类体内,也可写在类体外;
2)析构函数也是特殊的成员函数,没有参数,没有返回值,不指定函数类型;
3)一个类中只能有一个析构函数,析构函数不允许重载;
4)析构函数名必须与类名相同,但必须在前面加上 ~ ,来与构造函数区分;
5)析构函数是对象撤销时,由系统自动调用的;
在程序中,当遇到某个对象的生存期结束(调用方法结束)时,就会调用析构函数,再收回对对象的分配空间;
如果程序中使用了new关键字为对象开辟了空间,则类中应该定义析构函数,并在析构函数中使用delete方法删除new所开辟的空间(因为撤销对象时,系统自动收回为对象分配的空间,但不能自动收回new分配的动态存储空间);
class Str{
char *Sp;
int Length;
public :
Str(char *string){
if(string){
Length=strlen(string);
Sp = new char[Length + 1]; //在构造函数中将成员数据指针指向动态开辟的内存;
strcpy(Sp,string); //用初值为开辟的内存赋值;(为指针赋值)strcpy是拷贝,将第二个参数连同 字符串结束符 \n 一同拷贝给第一个参数;
}
else{
Sp =0;
}
void show( void ){cout<<Sp<<endl;}
~Str(){ if(Sp){delete []Sp;} } //析构函数,当释放对象时收回开辟的内存;
}
}
可以使用new为对象分配空间:如:
A *p;
p = new A;
delete p; //但此时必须用delete 删除 p,
new:调用了构造函数;
delete:调用了析构函数,并且是先调用析构函数,然后释放空间;
16,用new运算符动态生成数组时,自动调用构造函数,但使用delete释放对象数组的空间时,需要在指针变量的前面加 ‘ [ ] ’ ,如:delete [ ]Sp,否则释放的只是第0个元素的空间;
16,完成拷贝功能的构造函数:
可以在定义一个对象的时候,对另一个对象进行初始化,即构造函数的参数是另一个对象的引用,这种构造函数常为完成拷贝功能的构造函数;
完成拷贝功能的构造函数的一般格式为:
类名::类名(类名 &变量名){
//函数体完成对应数据成员的赋值;
}
class A{
float x , y ;
public :
A(float a =0 ,float b = 0){
x = a ; y =b;
}
A(A &a){ //形参必须是同类型对象的引用
x = a.x ;y = a.y; }
}
}
void main(void){
A a1(2.0 , 3.0);
A a2(a1); // 实参必须是同类型对象
}
隐含了拷贝的构造函数的情况:
class A{
float x ,y ;
public :
A (float a , float b){ x =a ;y = b;}
void print(void){ cout<<x<<'\t'<<y<<endl;}
~A(){ cout<<"调用了析构函数\n";}
}
void main(void){
A a1(2.0 , 3.0);
A a2(a1);
A a3 = a1; //可以这样赋值
a1.print(0;
a2.print();
a3.print();
}
当类中的数据成员使用new运算符动态的分配空间时,必须显示的定义具有拷贝功能的构造函数;
class Str{
char *Sp ;
int Length;
public :
Str (char *string){
if(string){
Length = strlen(string);
Sp = new char[Length+1]; //有new运算符
strcpy(Sp , string);
}
else Sp =0;
}
Str::Str(&s){
if(s.Sp){
Length = s.Length;
Sp = new char[s.Length];
strcpySp, s.Sp);
}
else Sp = 0;
}
void show(void){ cout<<Sp<<endl; }
~Str(){ if(Sp) delete []Sp; }
}
void main(void){
Str s1("hhhh");
Str s2(s1);
s1.show(); s2.show();
}
17,构造函数和对象成员:
class B{
};
class A{
...
B b1,b2; //类A 中包含类B 的对象;
};
对类A的对象进行初始化时,还要对B的对象进行初始化,所以类A的构造函数要调用B 的构造函数;
例:
class A{
float x ,y;
public:
void show(void){ cout<<x<<'\t'<<y<<endl; }
A( float a ,float b){ x = a ; y = b;}
}
class C{
float z;
A a1; //类C中包含类A的对象,即类C的数据成员是类A的对象
public:
C(float a ,float b ,float c): a1(b , c){ //a1(b , c ) 是利用类A的构造函数对类A的对象进行初始化;
z = a ; //对类C的对象进行初始化;
}
void show(void){ cout<<'z:'<<a<<'\t'<<endl; }
}
void main(void){
C c1(1.0, 2.0, 3.0);
c1.show();
}
格式:
类名::类名(args):其他类的对象名O1.(args1) , ...... , On(args n){ } ;
对对象成员的构造函数的调用顺序取决于这些对象成员在类中的声明顺序,与在成员初始化列表中的顺序无关;
初始化对象成员的参数(实参),可以是表达式,也可以仅对部分对象成员进行初始化;
class A{
float x , y;
public :
A(float a ,float b){ x = a; y = b ; }
void print(void){ cout<<"x:"<<x<<'\t'<<'y:'<<y<<endl;}
};
class B{
float x1, y1;
public :
B(float a , float b){ x1 = a ; y1 = b; }
void print(void){ cout<<'x1:'<<x1<<'\t'<<'y1:'<<y1<<endl; }
}
class C{
float z;
A a1;
B b1;
public:
C(float a , float b,float c ,float c ,float d ,float e): a1(a+b , c) , b1(a ,d ){ //对象初始化的参数可以是表达式;
z = e;
}
void show(void ){ cout<<'z:'<<a<<endl;}
}
void main(void){ C c1(1,2,3,4,5); }
17,继承:
基类或父类:原来已有的类;
派生类或子类:新建立的类;
c++中,一个派生类可以从多个基类派生,也可以从多个基类派生,即支持单继承和多继承;
Java 只支持单继承;
但派生并不是简单的派生,有可能改变基类的性质:
有三种派生方式:公有派生,保护派生,私有派生;默认的是私有派生;
从一个基类派生一个新类的格式:
class 派生类名 继承方式 基类名 { //派生类中新增的成员; } //继承方式有三种:公有继承,保护继承,私有继承;
public protected private
公有继承 public protected 不可见
私有I继承 private private 不可见
保护继承 protected protected 不可见
公有派生:
公有派生时,基类所有成员在派生类中保持各个类的访问权限;
基类 成员属性 派生类中 派生类外
公有 可以引用 可以引用
保护 可以引用 不可以引用
私有 不可以引用 不可以引用
保护派生:
保护派生时,基类中公有成员和保护成员在派生类中均变成保护的,只能被派生类成员函数或友元访问,;基类中私有成员在派生类中不可直接使用
私有派生:
私有派生时,基类中公成员和保护成员在派生类中都变成私有的成员,,在派生类中不可直接使用,基类中的私有成员在派生类中不可直接使用;
当类中的构造函数或析构函数声明为私有时,所定义的类是没有实用意义的,一般不能用它来产生对象或派生类;
17,抽象类:不能实例化,只能作为基类;
将类的构造函数和析构函数定义为保护的时候,这种类为抽象类;
18,派生类构造函数的调用顺序:
基类构造函数的调用->子对象类构造函数的调用->派生类构造函数的调用
19,多继承(多个基类派生一个类)时,当继承的类中有相同的函数或成员数据时,会发生冲突,调用时可利用域作用符::来解决;
class A{
public : int x ;
A(int a = 0){ x =a ;}
void show(void){ cout<<x<<endl;}
}
class B{
pubilc : int x;
B(int a = 0){ x = a;}
void show(void){cout<<x<<endl;}
}
class C : public A ,public B{
public : int y;
void setX(int a){ x = a ;}
void setY(int b){ y = b;}
void getY(){ return y; }
}
void main(void){
C c1;
c1.B::show(); //c1有两个show函数,所以使用类作用域符:: 来指明数据或函数 的来源;A::x = a;
}
20,支配规则:当派生类中新增加的数据或函数与基类中的成员数据或函数同名时,若不加限制,则优先调用派生类中的成员;
21,任意基类在派生类中只能继承一次,否则会造成成员名的冲突;
若在派生类中确实需要有两个以上的基类成员,则可以使用基类的两个对象作为派生类的成员;
把一个类作为派生类的基类和把一个类的对象作为一个类的成员,是有区别的:在派生类中可直接使用基类的成员(访问权限允许的话),但要使用一个对象成员的成员时,则必须在对象名后加上成员运算符" . "和成员名;
22,面向对象编程思想中:
接口实现多态:
interface A{
public int s1();
}
class B implements A{.....}
class C implemetns A{.....}
A a =new B(); a.s1();
A a= new C(); a.s1();
2,赋值兼容规则:
可以将派生类的对象赋值给基类对象,反之不行,只将从基类继承来的成员赋值;
可以将一个派生类的对象地址赋给基类的指针变量:
Base *basep; //基类指针,
basep = &d; //d是派生类对象;
基类指针只能引用从基类继承来的成员;
派生类对象可以初始化基类的引用:
Derive d; //派生类对象
Base basei = &d; //basei是基类引用;
27,虚基类(虚函数):
B继承了A;C继承了A;D继承了B和C;此时D 中就有两份A的拷贝;
在多重派生中,若使公共基类在派生类中只有一个拷贝,则可将该类声明为虚基类;
在派生类的定义中,只要在基类的类名前加上关键字virtual ,就可以将基类声明为虚基类;
class B :public virtual A{
public:
int y;
B(int a=0 ,int b = 0):A(b){ y=a; }
}
用虚基类进行多重派生时,若虚基类中没有缺省的构造函数,则在派生类中的构造函数中必须有对虚基类构造函数的调用(且首先调用);
由虚基类派生出的对象进行初始化时,直接调用虚基类的构造函数。:
class A{
public : int x;
A(int a){ x = a;}
};
class B: public A{
public : int y ;
B(int a =0, int b = 0) : A (a){ y = b;}
};
class C:public A{
public: int z;
C(int a = 0, int c = 0): A(a){ z= c;}
}
class D:public B ,public C{
public:int dx;
D(int a1 , int b, int c , int d ,int a2) : B(a1 , b) , C(a2 , c){ //直接在派生类中调用虚基类的构造函数 A(a2);
dx = d; //没有对虚基类构造函数的调用,就用缺省的构造函数
}
}
void main(void){
D d1(10 , 20, 30,40,50);
cout<<d1.x<<endl; //
d1.x = 400;
cout<<d1.x<<endl;
cout<<d1.y<<endl;
}
28,友元函数:
类中私有或保护成员不能在类外不能被访问;
友元函数:是一种定义在类外部的普通函数,特点是能够访问类中的私有或保护成员,即类的访问权限限制符对其不起作用;
友元函数需要在类体内进行说明,加上friend关键字;friend 函数类型 函数名(A &a);参数为对象名或对象的引用;
友元函数不是成员函数,所以用法和普通函数一样,但可以访问类中的所有数据;破坏了类的封装性和隐蔽性,使得非成员函数可以访问类的私有成员;
一个类的友元可以自由的访问类中的所有成员;
友元函数近似于普通函数,没有this指针,只能使用对象名或对象的引用 作为参数;
class A{
float x , y;
public:
A(int a ,int b){ x =a ; y = b; }
float getX(){ return x;}
flaot getY(){ return y;}
friend float sum(A &a){ return a.x +a.y ; } // 友元函数
float sum(){ return x+y; } //成员函数
}
float sumxy(A &a){ return a.getX()+a.getY() ;} //普通函数只能通过公有函数访问私有成员;
void main(void){
A t1(4,5), t2(10 , 20); //声明对象
cout<<t1.sum()<<endl; //成员函数的调用,利用对象名调用
cout<<sum(t2)<<endl; //友元函数的调用,直接调用;
}
友元函数注意:
1,友元函数只能在类内定义,函数体可在类内定义,也可在类外定义;
2,它可以访问类中的的所有成员(公有,私有,保护),但一般的函数只能访问公有成员;
通过友元函数实现多个类的联系:
类A中的某个成员函数是类B中的友元函数,则该成员函数可以直接访问类B中 的私有成员,从而实现类之间的沟通;
一个类的成员函数是另一个类的友元函数时,应该先定义友元函数所在的类;
class B; //必须先做引用性说明;
class A{}
28,友元等,详见 http://download.csdn.net/detail/qq_30840197/9629740?locationNum=3
29,虚函数:
多态性:调用同一个函数名,但是根据需要可以实心不同的功能;
多态性分为:编译时的多态性和运行时的多态性两种;
编译时的多态性:即函数的重载;
运行时的多态性:即虚函数;
运行时的多态性:是指在程序运行前,根据函数名和参数,无法知道具体调用哪个函数,必须在程序运行中,根据具体运行状况动态决定;
30,虚函数:可以在程序运行时通过相同的函数名实现不同功能的函数称为虚函数;
定义格式:virtual 函数类型 函数名(参数列表)
使用场合:若要访问派生类中的同名函数,则必须在基类中将该同名函数定义成虚函数;这样将不同的派生类的对象地址赋给基类指针变量后,就可以动态根据这种赋值语句(pa = &c ;pa->print(); )调用不同类中的函数;
一旦把基类中的成员函数定义为虚函数之后,由基类派生出来的所以派生类中,该函数均保持虚函数的特性;
在派生类中重新定义基类中的虚函数时,不需要virtual 关键字了;
虚函数是在基类中定义的用关键字virtual 修饰的protected或public成员函数;它可以在派生类中重新定义,以形成不同的版本;只有在程序的执行过程中,根据指针具体指向哪个类的对象,或依据引用哪个类的对象,才能确定激活哪个版本,实现动态聚束;
class A{
protected :int x;
public:
A( ){ x = 100; }
virtual void print(){ cout<<z<<endl;} //虚函数定义
}
class B : public A{
int y ;
public:
B(){ y = 200; }
void print(){cout<<y<<endl;} //派生虚函数
}
class C:public A{
int z;
public:
C(){ z = 300;}
void print(){ cout<<z<<endl;} //派生虚函数
}
void main(void){
A a , *pa; //实现这种动态的多态性时,必须使用基类的指针变量,使该指针指向不同的派生类对象,并且使指针指向不同的派生类对象,并通过调用指针指向的虚函数,实现动态多态性;
B b; C c; //声明对象
a.print(); b.print(); c.print(); //静态调用;
pa = &a; //使指针指向不同的派生类对象
pa->print(); //调用类A的虚函数 ,通过调用指针所指向的虚函数,来实现动态的多态性;
pa = &b ;
pa->print(); //调用类B 的虚函数
pa = &c ;
pa->print(); //调用类C 的虚函数
}
如果派生类重写的父类中的同名函数与父类中的同名函数的参数列表不同,则不是虚函数,是重载;参数的类型,顺序,参数个数,必须一一对应;函数的返回类型也必须一样;
class Base {
public :
virtual int set(int a , int b){ 。。。。}
}
class C :public Base{
public:
int set (int x , int y = 0){ 。。。。} //不是虚函数,是重载
}
虚函数注意:
1)虚函数必须是类的成员函数,不能是类的友元函数或静态的成员函数;
2)在派生类中没有定义虚函数时,与一般的成员函数一样,当调用虚函数时,直接调用基类中的虚函数;
3)可把析构函数定义为虚函数,但不能把构造函数定义为虚函数;
4)虚函数执行的较慢,因此,除了要编写通用的程序并且一定要使用虚函数才能完成该功能之外,一般不使用虚函数;
5)一个函数如果被定义成虚函数,不管经历多少次派生,仍保持虚特性;
6)利用对象名进行调用和非虚函数没有区别:c.Point::area();//基类 c.Circle::area();//派生类
7)如果派生类是私有派生的,则不能直接使用基类指针调用虚函数的形式;
29,纯虚函数:
在基类中不给出虚函数的具体实现,而是只给出虚函数的定义。此时基类中的虚函数只是一个入口,具体的目的的由派生类中对象决定;这就是纯虚函数;
class 基类名{
virtual 函数类型 函数名(参数列表) = 0; //纯虚函数的定义;
}
class A{
protected int x ;
public:
A(){ x= 100 ;}
virtual void print() = 0; //纯虚函数的定义;
}
class B : public A{
private int y;
B(){ y = 200; }
void print(){ cout<<y<<endl;}
}
class C:public A{
int z ;
public :
C(){ z = 300;}
void print(){ cout<<z<<endl;}
}
void main(void){
A *pa;
B b ; C c ;
pa= &b;
pa->print();
pa = &c;
pa->print(); //此处不能声明A 的对象 a,也就不能有pa = &a ,pa->print() , 因为包含至少一个纯虚函数的类是抽象类;
}
纯虚函数的注意:
1)定义纯虚函数时,不能定义函数体;
2)将纯虚函数的函数名赋值为0,本质上是将指向函数体的指针值赋为0;
3)把至少包含一个纯虚函数的类称为抽象类,这种类只能作为派生类的基类,不能声明该类自己的对象;但可以定义指向该抽象类即该基类的指针,当用这种指针指该抽象类的派生类的对象时,需要在派生类中对纯虚函数进行重载,否则会报错;
4)派生类中必须有纯虚函数的实现部分,否则该派生类不能创建对象;
30,静态数据成员:
使用关键字static修饰;可直接使用类名调用,调用形式:在类外:类名::静态成员名 ; 在类内可直接引用;非静态成员使用 对象.非静态成员名 来引用
c++中静态数据成员有缺省的初值是0;
静态数据成员有全局变量和局部变量的的一些特性,但全局变量在程序中的的任何位置都可以访问, 但静态数据成员是受访问权限约束的,必须是public权限时,才能在类外进行访问;;
静态成员函数:使用关键字static 对类的成员函数进行修饰;与静态数据成员一样,在类外可以使用类名::静态成员函数名 来直接调用;类内可直接引用;
静态成员函数的实现部分在类定义之外定义时,不能加static 关键字。
不能把静态成员函数定义为虚函数;可将静态成员函数定义为内联的inline,定义方法与非静态成员函数定义一样;
31,const,volatile对象和成员函数:
用const修饰的对象,只能访问该类中用const修饰的成员函数,其他的成员函数是不能访问的;用volaile修饰的对象,只能访问该类中用volatile修饰的成员函数,不能访问其他的成员函数;
当希望成员函数只能引用成员数据的值,而不允许成员函数修改成员数据的值时,可用关键字const修饰成员函数;
32,指向类成员的指针:
在c++中,可以定义一种特殊的指针,用来指向类的成员函数或类中的数据成员,并可通过这样的指针来使用类的成员函数或类中的数据成员;
指向类中数据成员的指针:
定义一个指向类中数据成员的指针变量的格式:
指针所指向数据的类型 类名::*指针名; //指针所指向数据的类型 必须是类中某一数据成员的类型;
1,指向类中数据成员的指针变量不是类中的成员,所以这种指针变量要在类外定义;
2,与 指向类中数据成员的指针变量 同类型的任一数据成员,可将其地址赋给这种指针变量,赋值的格式为:PointName = &ClassName :: member; //这种赋值 ,是取此类中member成员相对于该类所在对象的偏移量,即相对地址,赋给指针变量;
3,用这种指针访问数据成员时,必须指明是哪个对象的数据成员; 用法:ObjectName . *PointName;
4,这种指针不是类的成员,所以只能访问公有数据,不能访问私有数据;
指向类中成员函数的指针变量:
格式: type (ClassName :: *PointName) (参数列表)
PointName :是指向类中成员函数的指针变量;
type:通过指针PointName调用类中成员函数时返回值的数据类型,它必须与类中某个成员函数的返回值类型一致;
在使用这种指针前,要对其进行赋值: PointName = ClassName :: FuncName; 同样只是将指定成员函数的的相对地址赋给指针;
调用格式:(对象名.指针)(参数)的形式;
比较:int max (int a , int b){ return a>b?a:b ; }
若有:int *f(int , int ); f =max ;
则调用时 ,*f(x , y);
所以:(s1.PointName)();
对指向成员函数的指针变量的使用方法说明几点:
1)指向类中成员函数 的指针变量不是类中的成员,这种指针变量要在类外定义;
2)不能将任意成员函数的地址赋给指向成员函数的指针变量,只有成员函数的参数个数,顺序,类型及函数类型均与指针变量相同时,才能将成员函数的指针赋给该指针变量;
3,)使用这种指针变量调用成员函数时,必须指明调用哪个对象的成员函数;这种指针变量是不能单独使用的,必须用对象引用;
4,只能调用公有成员函数,调用私有成员函数时会报错;
5,当一个成员函数的指针指向一个虚函数,且通过指向对象的基类指针或对象的引用来该成员函数指针时,同样的产生运行时多态;
6,当用这种指针指向静态成员函数时,可直接使用类名而不列举对象名;
31,运算符的重载:就是赋予运算符多重含义;
对象之间不能使用“+” ,必须对运算符进行重载;
为了重载运算符,必须定义一个函数来对运算符进行操作,使得当使用该运算符时就调用该函数,该函数就是运算符重载函数;它通常是类的成员函数或友元函数;运算符的操作数应该是类的对象;
重载为类的成员函数:
类名 operator 运算符(参数) { //函数体 }
A operator +(A &); //重载了类A的 “+”运算符;
operator是关键字,与后面的运算符 (此处是+)构成了函数名;
当用成员函数实现运算符的重载时,运算符重载函数的参数只能有两种情况:没有参数或带有一个参数;详见 http://download.csdn.net/detail/qq_30840197/9629740?locationNum=3 第853页;
40,抽象的实现:通过类的声明;
封装的实现:通过类声明中的大括号 { };
继承的实现:通过声明派生类;
多态的实现:通过重载函数和虚函数;
41,公有类型成员:是类与外部的接口,任何外部函数都可以访问公有类型成员;
C++ 部分知识点的更多相关文章
- ASP.NET Core 中的那些认证中间件及一些重要知识点
前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...
- ASP.NET MVC开发:Web项目开发必备知识点
最近加班加点完成一个Web项目,使用Asp.net MVC开发.很久以前接触的Asp.net开发还是Aspx形式,什么Razor引擎,什么MVC还是这次开发才明白,可以算是新手. 对新手而言,那进行A ...
- UWP开发必备以及常用知识点总结
一直在学UWP,一直在写Code,自己到达了什么水平?还有多少东西需要学习才能独挡一面?我想对刚接触UWP的开发者都有这种困惑,偶尔停下来总结分析一下还是很有收获的! 以下内容是自己开发中经常遇到的一 ...
- C#高级知识点&(ABP框架理论学习高级篇)——白金版
前言摘要 很早以前就有要写ABP高级系列教程的计划了,但是迟迟到现在这个高级理论系列才和大家见面.其实这篇博客很早就着手写了,只是楼主一直写写停停.看看下图,就知道这篇博客的生产日期了,谁知它的出厂日 ...
- lucene 基础知识点
部分知识点的梳理,参考<lucene实战>及网络资料 1.基本概念 lucence 可以认为分为两大组件: 1)索引组件 a.内容获取:即将原始的内容材料,可以是数据库.网站(爬虫).文本 ...
- DoraCMS 源码知识点备注
项目需要研究了下DoraCMS这款开源CMS,真心做的不错:).用的框架是常用的express 4 + mongoose,代码也很规范,值得学习. 源码中一些涉及到的小知识点备注下: https:// ...
- atitit 商业项目常用模块技术知识点 v3 qc29
atitit 商业项目常用模块技术知识点 v3 qc29 条码二维码barcodebarcode 条码二维码qrcodeqrcode 条码二维码dm码生成与识别 条码二维码pdf147码 条码二维码z ...
- HTML5知识点总结
HTML5知识点总结(一) 一.HTML新增元素 1.IE9版本以下支持HTML5的方法 <!--[if lt IE9]> <script src="http://cdn. ...
- JavaScript易错知识点整理
前言 本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一 ...
- Sqlserver中一直在用又经常被忽略的知识点一
已经有快2个月没有更新博客了,实在是因为最近发生了太多的事情,辞了工作,在湘雅医院待了一个多月,然后又新换了工作...... 在平时的工作中,Sqlserver中许多知识点是经常用到的,但是有时候我们 ...
随机推荐
- navigator获取参数
<script type="text/javascript" language="javascript"> document.write(" ...
- socket串口通信
SocketServer: #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #inclu ...
- NUnit使用方法
单元测试是一个成熟项目必不可少的一个环节,NUnit很好的提供了测元测试的一些方法,以下是我得出的一点点经验以及NUnit的一点点实际应用中用到的内容.写的有点儿乱,不懂下面留言.谢谢~ 准备NUni ...
- C#编程断点续传
C#编程总结(十二)断点续传 Posted on 2014-02-16 10:56 停留的风 阅读(384) 评论(3) 编辑 收藏 C#编程总结(十二)断点续传 我们经常使用下载工具,如bit精灵. ...
- C#简单实现贪吃蛇程序(LinQ + Entity)
做梦想起来的C#简单实现贪吃蛇程序(LinQ + Entity) 最近一直在忙着单位核心开发组件的版本更新,前天加了一个通宵,昨天晚上却睡不着,脑子里面突然不知怎的一直在想贪吃蛇的实现方法.以往也有类 ...
- iOS基础 - 史上最难游戏
步骤一:隐藏状态栏 步骤二:屏幕适配 步骤三:设置窗口的根控制器为导航控制器,并且设置导航条和状态栏. 步骤四:搭建设置界面 步骤五:控制器连线 步骤六:搭建关卡控制器 加载pilst文件 创建关卡模 ...
- 【学习笔记】锋利的jQuery(二)DOM操作
一.获取DOM节点 //找祖宗 parent() parents() closest() //找后代 children(); find(); //找兄弟 next()/nextAll() prev() ...
- 调WScript.Shell时报错:Automation 服务器不能创建对象
我们经常需要通过生成ActiveXObject("WScript.Shell");来调某一exe文件, 如 //设置网页打印的页眉页脚为空 var HKEY_Root,HKEY_P ...
- ASP.NET MVC中使用Ninject
ASP.NET MVC中使用Ninject 在[ASP.NET MVC 小牛之路]系列上一篇文章(依赖注入(DI)和Ninject)的末尾提到了在ASP.NET MVC中使用Ninject要做的两件事 ...
- Lua里的"switch-case"语句
Lua本身并没有提供switch-case语句,难道说我们就只能靠不断的"if ... elseif ... "这样冗长的方式来实现选择的功能么?当然不是这样的.Lua提供了功能强 ...