C++基础笔记(一)
一、一些基础知识点
1、赋值构成一个表达式,具有值,其值为赋值符左边表达式的值。表达式和语句的一个重要区别是,表达式有值,而语句没有值。
2、隐式类型转换总是朝着表达数据能力更强的方向,并且转换总是逐个运算符进行的。如
float f=3.5;
int n=6;
long k=21;
double ss=f*n+k/2;//在计算ss时,首先将f和n转换成double,算得21,然后计算k/2得10,再将10(long int)转换成double,最后得31。
有符号数向无符号数转换。
3、当又有声明又有定义时,定义中不允许出现默认参数,如果函数只有定义,才允许默认参数出现在定义中。默认值可以是一个全局变量,全局常量,或是一个函数,但是不能是局部变量,因为默认参数的函数调用是在编译时确定的,而局部变量的位置和值在编译时是无法确定的。如
int a=1;
void fun()
{
int i;
void g(int x=i); //not right
int g(int x=a); //right
}
4、define宏定义指令
1) 使用其定义常量已被C++中的const定义语句所代替。
2) 用来定义带参数的宏,已被C++的内联函数所代替。
3) 其一个有效的使用是在条件编译中。
5、编译时必须知道数组的大小。如
int a[]={1,2,3,4,5};//编译器会自动去数
for(int i=0;i<sizeof(a)/sizeof(int),i++)
...
6、C++会把注释当做空格。
7、Free和malloc
int* a;
a=(int*)malloc(sizeof(int));
cout<<a<<endl;
free((void*)a);
int* b;
b=(int*)malloc(12*sizeof(int));
cout<<b<<" "<<b+1<<" "<<b+2;
free((void*)b);
8、指向常量的指针,指针常量,指向常量的指针常量
指向常量的指针:通过指针不能改变所指向的变量的值,但是指针的值可以变化(即指针可以指向其它变量的地址)。形式如const int* pi=&a;原来的变量的访问属性也不会发生改变,如原来是普通变量,则其值可以变化,原来是常变量,则其值不能变化。
指针常量:在定义时必须初始化,且一旦赋值,则以后该指针的值将不会再发生变化(即不能再指向其它地址)。形如char* const pc="abcd"; 注意,pc的值是不能再改变了,但是*pc的值可以变化,如*pc=b;
指向常量的指针常量:具有上述两具指针的特点,须在定义时初始化,且一旦赋值,则以后该指针的值将不会再发生变化,且通过指针不能改变所指向的变量的值,形如const int* const cpc="perfect";注意,这种情况下,*cpc的值也是不能改变的,如*cpc=n是不对的。
int main()
{
char* const pc="abcd";
cout<<pc<<endl;
cout<<*pc<<endl;
//*pc='b'; //有些编译器可以通过,但是结果不对的
//cout<<pc<<endl;
const char* const cpc="perfect";
cout<<cpc<<endl;
cout<<*cpc<<endl;
*cpc='n';//not right
cout<<cpc<<endl;
return 1;
}
9、sum(int array[],int n)与sum(int* array,int n)是等价的。
10、函数返回值,可以返回堆地址,也可以返回全局或静态变量的地址,但是不能返回局部变量的地址。
11、void指针是空类型指针,它不指任何类型,它仅仅是一个地址,不能进行指针运算,也不能进行间接引用。
NULL与void* 是不同的概念,NULL是一个指针值,任何类型的指针都可赋予该值。而void* 是一种类型(语法上是一个类型,本质上不是,没有任何一个变量或对象,其类型为void),是一种无任何类型的指针。不允许对void进行引用。
12、由引号(" ")标识,但不是用来初始化数组的字符串,是字符串常量,如cout<<"hello"<<endl;中,"hello"就是字符串常量。字符串常量的类型是指向字符的指针(char*),它与字符数组名同属于一种类型。
由于字符串常量的地址属性,两个同样字符组成的字符串常量是不相等的, 字符串常量的比较是地址的比较。
字符串常量,字符数组名(常量指针),字符指针均属于同一种数据类型。
13、不能建立引用的数组,因为数组是某个数据类型的集合,数组名表示起始地址,它不是数据类型。如
int a[10];
int& a1[10]=a; //not right
引用本身不是一种数据类型(int&不是类型,定义时也不产生内存空间),所以没有引用的引用。也没有引用的指针。引用是变量或对象的引用,而不是类型的引用,所以有空指针,但是没有空引用。
Int& ri=NULL //没有意义
可以用引用返回值。在通常情况下,C++会建立一个临时变量以将函数返回值带回。但是如果用引用,则不用建立临时变量。
C++规定,临时变量或对象的生命周期在一个完整的语句表达式结束后便宣告结束。所以如果以返回值初始化一个引用,应当先建立一个变量,将函数返回值赋于这个变量,作如下处理:
int x=fn1(5.0); //在这一句后临时变量生命周期将结束
int& b=x;
*************************
float temp;
float& fn2(float r)
{
temp=10*r;
return temp;
}
用如上的方法,则函数返回值将不再创建临时变量,而直接与全局变量temp共享内存单元
*******************************
以引用的初始化,可以是变量,也以是常量,也可以是一定类型的堆空间变量,但是引用不是指针,如下表达是不对的:
int& a=new int(2); //not right
下面是用堆空间变量初始化引用的一个例子:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
double* pd=new double;
if(pd==NULL)
{printf("failed");exit(1);}
cout<<pd<<" "<<*pd<<endl;
double& rd=*pd;
//...
{
cin>>rd;
cout<<rd<<endl;
cout<<&rd;
}
delete& rd; //或delete pd
return 1;
}
注意:用double& rd来定义引用,而&rd则是取引用所指向的空间的地址。
14、声明一个结构并不分配内存,内存分配发生在定义这个新数据类型的变量中。结构不像数组,结构变量不是指针,&结构变量 取到的是结构中第一个成员变量的地址。结构变量可以相互赋值。结构成员不能作自身的结构变量,但是可以用结构指针作为成员。
15、将类定义和其它成员函数定义分开,是目前开发程序的通常做法。我们把类定义(头文件)看成是类的外部接口,类的成员函数定义看成是类的内部实现。
类是一个抽象的概念,并不是一个实体,并不含有属性值,而只有对象才占有一定的空间,含有明确的属性值。
16、要想共享初始化的过程,可以先定义一个共享成员函数,然后每个构造函数都调用之。
17、C++提供的默认构造函数是个无参构造函数,它仅负责创建对象,而不做任何初始化工作。只要一个类定义了一个构造函数,C++就不再提供默认的构造函数。(如果此时还想要无参构造函数,则需要自己定义)
与变量定义类似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为0,否则对象值是随机的。
创建对象的唯一途径是调用构造函数。
静态对象只被构造一次,所有全局对象都在主函数main()之前被构造。
18、面向对象程序设计主要是两方面:面向对象应用程序设计,类库的设计。面向对象程序设计的关键是如何抽象和分类。
19、全局变量、静态变量、常量存放在全局数据区,所有类成员函数和非类成员函数代码存放在代码区,为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区,余下的空间都被作为堆区。
void* malloc(size_t);和void free(void*);在头文件malloc.h中声明。而操作符new和delete是C++的一部分,无须包含头文件,它们都是从堆中分配和释放内存块,但是具体操作上两者有很大的区别。
操作堆内存时,如果分配了内存,就有责任回收它,否则运行的程序将会造成内存泄露,这与函数中栈区分配局部变量有本质的区别。
从C++来说,不使用malloc()函数一个原因是,它在分配空间的时候不能调用构造函数。类对象的建立是分配空间,构造结构及初始化的三位一体,它们统一由构造函数来完成。而new和delete在创建对象和删除对象时,便同时调用构造函数和析构函数。
定义对象数组,在生成对象时,依次调用构造函数(如依次生成ps[0],ps[1],ps[2]......),由于分配数组时,new的格式是类型后面跟[元素个数](student* ps=new student[10]),不能再跟构造函数参数,所以从堆上分配对象数组,只能调用默认的构造函数,不能调用其它任何构造函数,如果该类没有默认的构造函数,则分配对象数组失败。Delete[] ps告诉C++将要该指针指向的是一个数组,如果在[]中填上了长度信息,C++将忽略。
20、拷贝构造函数
当构造函数的参数为自身类的引用时,这个构造函数称为拷贝构造函数。拷贝构造函数的功能是用一个已有对象初始化一个正在建立的同类对象。
拷贝构造函数定义形式如下:
Student(student& s)
Person p1;
p2=p1;
在创建对象p2时,对象p1被复制给了p2,同时资源也作了复制,此时p1和p2指向不同的资源,这称为深拷贝。
如果你的类需要析构函数来析构资源,则它也需要一个拷贝构造函数。C++提供的默认函数只是对对象进行浅拷贝复制。如果对象的数据成员包括指向堆空间的指针,就不能使用这种拷贝方式,要自己定义拷贝构造函数,为创建的对象分配堆空间。
21、静态成员
这种属于类的一部分,但既不适用于普通成员函数,也不适用于全局变量表示的数据,我们用静态成员来表示。
一般情况下,我们在类的内部实现中对静态数据成员进行定义(在类的内部实现中分配空间和初始化)。
Int student::noOfstudent=0;
静态数据成员一般用于:
标志一个事件的发生与否,某个特定的指针,变化的对象等。
静态成员函数定义是类的内部实现,属于类的一部分,定义位置同一般成员函数。与静态数据成员一样,静态成员函数与类相联系,不与类的对象相联系,所以访问静态成员函数时,不需要对象。如果用对象去引用静态成员函数,只是用其类型。
#include <iostream>
using namespace std;
class Student
{
public:
static int number()
{
return noOfStudents;
}
protected:
char name[40];
static int noOfStudents;
};
int Student::noOfStudents=1;
int main()
{
Student s;
cout<<s.number()<<endl;
cout<<Student::number()<<endl;
return 1;
}
一个静态成员函数不与任何对象相联系,故不能对非静态成员进行默认访问。静态成员函数与非静态成员函数的根本区别是静态成员函数没有this指针。 这也就是静态成员函数与当前对象无联系的原因。
*********************************
class Sc
{
public:
void nsfn(int a);//类同声明成Sc::nsfn(Sc* this,int a)
static void sfn(int a); //无this指针
//...
};
void f(Sc& s)
{
s.nsfn(10); //C++编译成Sc::nsfn(&s,10)
s.sfn(10); //C++编译成Sc::sfn(10)
}
静态的static一词与静态存储类的static是两个概念,一个论及类,一个论及内存空间的位置及作用域,所限定以要区分静态对象和静态成员。