定义头文件时必须加:
#ifndef AAA_H
//所需要的头文件、宏定义、变量定义
//其中AAA为.c文件的文件名
#endif
C++编程中的声明为virtual函数,往往是由系统自动调用的,当然我们也可以调用。
我们也可以重新实现它,当系统需要自动调用这个虚函数时,会自动调用这个我们自己定义的函数。例如调用repaint()函数时,系统会自
动调用虚函数paintEvent()函数,虚函数QTcpServer::incomingConnection(int socketDescriptor )会被系统自动的调用,当有新的连接
到来时。当系统有新的连接并调用这个函数之后,newConnection() 信号被发送,且如果调用nextPendingConnection()会返回非空的
QTcpSocket对象指针。socketDescriptor是系统传递进来的一个文件描述符,代表一个可用的连接。我们可以重新实现这个函数,处理我们
自己的一些事件。
The base implementation creates a QTcpSocket, sets the socket descriptor and then stores the QTcpSocket in an internal
list of pending connections. Finally newConnection() is emitted.
round()四舍五入函数
RAND_MAX是VC中stdlib.h中宏定义的一个字符常量: #define RAND_MAX 0x7FFF 其值最小为32767,最大为2147483647 通常在产生随机小数
时可以使用RAND_MAX
static int a=0;//静态变量必须开始就初使化
对于类中的静态变量,必须在完成定义类之后就进行初使化,即在类外,否则编译无法通过
<数据类型> <类名> ::<静态数据成员名>=<值>
访问权限public private 型仍意都可,但是初使化之后的访问与访问权限有关
计算机名称和用户名不同,linux有一个默认的用户名,为root
指针在初使化的时候可以直接赋数值:如int *p=0; 过后,赋值非零要这样用p=(int *)11 表示p的内存地址,如果赋值0,也可以
但是其它数值不行
用"int x = rand() % 100;"来生成 0 到 100 之间的随机数这种方法是不或取的,比较好的做法是: j=(int)(n*rand()/(RAND_MAX+1.0))
产生一个0到n之间的随机数
void *fun(void)
{
//返回值指向一个void类型的指针
retrun (void *)0;
}
指针函数,且是返回一个指针,即有返回值,可以用void *(*p)(void)来指向它
返回指针函数的类定义:
int *ClassName::fun(){ }
结构体仍然有构造函数,与类一样,同时,一个类中,如果没有特别指出类型,都应该是private数据
std::string s1,s2;
//常用的有
s1.size();//返回字符串长度
s1=s1+s2;//字符串连接,perl中以.为连接两个字符串,以+为数字相加
s1=s2;//字符串赋值
if(s1==s2)//判断s1和s2是否相
int fun(){
throw A;
//code1
}
try{
fun();
//code2
}
catch(数据类型 变量){ //变量可以不要
//code3
}
//code4
catch 需要紧跟在try块之后,throw可以单独放在一个函数中,当try块调用包含throw的函数时,一旦匹配,就会跳到catch中,且catch函
数中的代码执行完毕后,不会再返回到throw后的代码执行,而是回到catch之后的代码执行
匿名对象构造方法:ClassName(参数) 或者new ClassName(参数) ,只是第一个反回的是一个对象,第二个是对象的地址
系统在一个构造函数也没有的情况下,会提供一个默认的构造函数
系统在一个拷贝构造函数也没有的情况下,也会提供一个默认的拷贝构造函数
系统在一个 = 等号重载运算符函数也没有的情况下,也会提供一个等号重载函数,其只有简单的值复制功能,但两个对象的地址空间不同
析构函数:基类的析构函数都要定义为虚函数,其子类自动为虚函数 virtual ~ClassName();
class A
{
public:
virtual ~A() { cout<<"A::~A() Called.\n"; }
};
class B : public A
{
public:
B(int i) { buf = new char[i]; }
virtual ~B()
{
delete [] buf;
cout<<"B::~B() Called.\n";
}
private:
char * buf;
};
void fun(A *a)
{
delete a;
}
void main()
{
A *a = new B(15);
fun(a);
}
执行该程序输出如下结果:
B::~B() Called.
A::~A() Called.
如果类A中的析构函数不用虚函数,则输出结果如下:
A::~A() Called.
当说明基类的析构函数是虚函数时,调用fun(a)函数,执行下述语句:
delete a;
由于执行delete语句时自动调用析构函数,采用动态联编,调用它基类的析构函数,所以输出上述结果。
当不说明基类的析构函数为虚函数时,delete隐含着对析构函数的调用,故产生输出:
A::~A() Called.
Base *p=new Derive;
构造函数不能为虚函数,构造函数是从派生类的基类开始,然后再到派生类,构造完成以后,再赋值
一个类的内部构造是按照它的数据成员的定义顺序执行构造的,数据成员中有对像,那么也会按顺序构造它,最后才执行它本身的构造函数
,其中常量数据成员只能在参数列表中初始化
访问类型为private型的数据与函数成员子类不能直接继承获得
访问类型为public型的数据与函数成员子类能够直接获得
访问类型为protected型的数据与函数成员子类能够直接获得
继承类型为private型的继承,该子类可以继承基类的public与protceted型数据与函数成员,但
继承后的成员则变为private,不能再被后面的子类继承
继承类型为protected型的继承,该子类可以继承基类的public与protceted型数据与函数成员,但
继承后的成员则变为protected,能被后面的子类继承
继承类型为public型的继承,该子类可以继承基类的public与protceted型数据与函数成员,继承
后的成员访问类型不变,能被后面的子类继承
儿子不能继承父亲的构造和析构函数
(1) 创建父类的对象,构造函数的执行和子类没有关系。
(2)创建子类的对象,构造函数的执行顺序:
(i)首先执行父类的构造函数,因为子类首先要从以public protected继承的父类中获得数据成员和函数成员,获得的数据成员
可以在该子类中直接使用,因为获得的数据成员就是该类的一部分
(ii)再是对数据成员进行初使化,包含它的对象成员,成员之间是按照它们的定义顺序先后初使化,而不是参数列表顺序,
参数列表应当按照从基类到以数据成员定义顺序为传值顺序,否则会编译出现警告
(ii)最后执行子类的构造函数
(iii)析构函数的执行顺序和构造函数相反,且基类的析构函数必须为虚函数,但构造函数一定不能为虚函数
典型的继承事例:
#include <iostream>
using namespace std;
class B
{
public:
B(int x=0):b(x)
{
cout<<"my is B\n";
cout<<"b is "<<b<<endl;
}
// private:
protected:
void B_fun()
{
cout<<"my is B function\n";
}
int b;
};
class A :public B
{
public:
A():B(4444),VAR(124),a(1111),bb(2222)//首先初使化基类,国为它要先从基类获得数据和函数成员,然后按照定义的数据成员进行
构造
{
cout<<"my is A\n";
}
int function()
{
cout<<a<<endl;
cout<<"my is b "<<b<<endl;//b为基类中的数据成员,这里直接使用
B_fun();//直接使用基类继承的函数,相当于它就是该类的一个函数
return 0;
}
private:
int VAR;
int a;
B bb;//基类的对象又作为一个数据成员,在这个对象构造之前,A类已经有了B基类的数据和函数成员,但那是直接继承而来,这里不
是继承而来,而是构造
};
int main()
{
A aa;
aa.function();
return 0;
}
#ifdef A(标识符) //只要A有定义,不管它是什么,就执行code1,否则code2
//code1
#else
//code2
#endif
宏函数是做的简单的替换操作,是在预编译阶段进行的,而inline函数是具体的函数,是在编译过程中进行的,有返回值
宏函数的一般格式:
#define A(x) (含x的具体表达式,如x*x,x+3等等) //外部一定要加括号,不然预编译时可能出现问题
virtual inline函数都必须在定义(实现)之前进行声明,并且,声明之后,具体的实现函数可以不加virtual inline,但是类的const函
数声明和定义都必须要加const在函数的末尾
纯虚函数是在一个类中,类似有这样的声明virtual void fun()=0; 纯虚函数所在的基类不能有实例对像,因为纯虚函数是不能在基类中
实现的,如果它的子类能实现,则只需要在它的子类中声明virtula void fun();即可;如果基类是虚函数,继承它的子类不用再次用
virtual声明
函数声明一定要放在主函数之前,因为系统要先识别才能调用,函数定义则是对函数的解释,是函数算法的说明,也就是它的具体的实现过
程,可以放在主函数后面。
预编译:导入相关头文件
编译:.c编译为.s
汇编:.c汇编成.o
连接:由多个.o生成.d文件
iostream和iostream.h的区别 前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发
现,里面的代码是不一样的。 后缀为.h的头文件c++标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里,声明在带.h
后缀的头文件里,c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。 因 此,当使用<iostream.h>时,相当于
在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;当使用< iostream>的时候,该头文件没有定义全局命名空间,必须使
用using namespace std;这样才能正确使用cout。
<string.h>是旧的C 头文件,对应的是基于char*的字符串处理函数;
<string>是包装了std 的C++头文件,对应的是新的string 类;
<cstring>是对应于旧C 头文件的std 版本。
C++转换类型方法:static_cast<需要转换为的类型>(需要转换的变量) //括号都不能省略
ClassName *p=new ClassName() //括号中给的是构造函数的参数
重点:用了new分配空间的堆空间作用域的全局,空间不会自动释放,这点不同于采用一般的方法构造对象,程序退出时候会自动的析构并
释放空间,采用new 必须使用delete在合适的地方析构并释放空间
且一般子函数中使用的new,那么应该在子函数结束末尾使用delete
delete p;//释放
ClassName *p=new ClassName[N] //N为数组长度,会调用无参构造函数,如果没有无参构造函数,则调用系统默认构造函数
delete []p;//释放
构造函数可以以参数默认的形式出现,此时如果构造的对象没有带参数,系统会自动调用这个以默认参数形式出现的构造函数
如果参数带了值,依次替换,下面是一个实例,默认参数只能在实现它的具体函数中给出,或者只定义中给出,但实现函数中不要有
class Tdate{
public:
Tdate(int m,int d,int y);
protected:
int month;
int day;
int year;
};
Tdate::Tdate(int m=4,int d=15,int y=1995):month(m),day(d),year(y)
{
cout<<month<<"/"<<day<<"/"<<year<<endl;
}
int main()
{
Tdate aday;
Tdate bday(20);
Tdate cday(3,12);
Tdate dday(1,2,1998);
cout << "Hello world!" << endl;
return 0;
}
系统一般不会替代默认构造函数,只有在一个构造函数都没有的前提下,系统才会提供
C++系统会提供会提供一个默认的构造函数,一个默认的拷贝构造函数,一个默认的赋值运算符,前提是他们三个分别都一个都没有,后两
个默认情况下都是浅拷贝,包括String类,除非自己定义的函数中使用引用传递 &
strncpy函数:多个n代表可以指定字符个数进行赋值。原型:char * strncpy(char *dest, char *src, size_tn); 功能:将字符串src中
最多n个字符复制到字符数组dest中
strcpy函数:顾名思义字符串复制函数:原型:extern char *strcpy(char *dest,char *src); 功能:把从src地址开始且含有NULL结束符
的字符串赋值到以dest开始的地址空间,返回dest(地址中存储的为复制后的新值)。
int strcmp(const char* str1, const char* str2);
这个函数是C标准库的函数,处理的是C风格0结尾字符数组字符串。
当s1<s2时,返回值<0 当s1=s2时,返回值=0 当s1>s2时,返回值>0
char *strcat(char *dest,char *src);
功能
把src所指字符串添加到dest结尾处(覆盖dest结尾处的'\0')并添加'\0'。
说明
src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
在读取文本内容时,\n单独作为两个字符 \ n,而在直接读取字符串中,\n是一个字符,
\n在输入函数中为两个字符,分别是\ n,但是在输出函数中,却是一个换行符,在输入中,实际的按一下回车,才表示一个换行符,而在
输出中,\n就表示换行
fopen失败返回NULL,而open失败返回-1,小于0
stdin stdout stderr 都是指针
fgets(str,n,p);读入的实际数据只有n-1个字节,因为这是一个字符串操作函数,所以它会中第n个字节中自动加上'\0'字符,总共n个字节
fputs(str,p);//输出到指针指向的文件,遇到换行符即结束,如果如果srt中的\n是从文本中读取的两个字符,那么它就是那个字符\和n,
如果是字符串的\n,那么表示一个字符
rewind(p);//使位置指针重新返回文件的开头
fseek(p,位移量,起始点);//起始点用0 1 2 设置,0代表开始位置,1代表当前位置,2代表文件末尾
fprintf(p,格式字符串,输出列表);//输出到磁盘文件
fscanf(p,格式字符串,输出列表);//从磁盘中读取
fscanf(fp,"%d,%6.2f",&i,&t);//那么在磁盘上的数据应该是类似这样存放的:3,4.5
fscanf(fp,"%d,%6.2f\n",&i,&t);//那么在磁盘上的数据应该是这样存放的:3,4.5\n 这里的\ n是真实的两个不同的字符,不是换行
fread(buffer,size,count,fp);//从磁盘中读取存放到buffer中
fwrite(buffer,size,count,fp);//写到磁盘中
const 和&定义时都必须要初始化,定义即要赋值,除非在一个文件中定义了const int a=10;或int& a=10;那么在另外一个引用中需要这样
声明:extern const int a;或者extern int&;
还有一种可能,就是在一个类中,如果定义了常量数据成员,如:const int a;那么它的初使化只能在构造函数的参数列表中初使化,如:
Student::Student(int x,int y):a(x);这里的a就可以是一个const成员。对常量数据成员的引用可以这样:object.a;但不能修改它的值
c/c++如果赋值等式右边的值是一个const常量,那么,它的左边的变量不能对右边的常量进行修改,这样,如果,左边是对右边的引用&,那
么必然左边也要用const声明,因为引用,左边可以修改右边的变量,用const声明以后,左边的变量也就变成了一个只读的常量
另外,如果右边是一个指向常量的指针,或者是一个返回指向常量的函数,即const int *p;或者const int *fun();,那么,赋值等式左边
必须声明为const int *p;类型,同样是因为左边的值可以修改右边的值,同理,int *const p;或者int *const fun();这是指针常量,它
们返回的仍然是一个指针,但指针是一个常量,左边仍然要声明为:int *const p;
如果是一个函数的引用返回,包含const引用返回,引用返回是一个值的直接复制过程,一个值复制给另外一个值,那么左值不会有机会修改
右值,另外引用和指针不可能同时出现
c++的静态成员函数只能够使用静态成员数据和静态成员函数
抽象类:如果一个类中有一个及其以上的纯虚函数,如:virtual int fun()=0;这个函数不需要在该类中实现,就可以定义为纯虚函数。
抽象类一般不能够直接定义对象,因为它有函数没有实现,如果在它的子类中能够有条件实现,那么还需要在子类中声明:
virtual int fun();并用具体的代码实现它,这点不同于虚函数,虚函数只需要在基类中声明:virtual int fun();类似的语句即可
虚函数的作用就是实现多态性,即父类和子类中有相同的函数名,相同的函数名实现相同或者不同的功能,这样可以使用基类指针,父类和
所有子类对象的地址都可以赋值给基类指针,基类指针会自动判断调用各自的函数,当然,如果直接使用各自的对象调用,不需要声明为虚
函数也可以正常调用各自的函数,不会产生混乱,但仅用一个基类指针调用父类和所有子类对象的函数时,必须在基类中声明为虚函数
BaseClass *p=new BaseClass;
p也可以接受子类的赋值,如:
p=new DeriveBaseClass; //小类到大类,可以自动转换,如int可以自动转换为long,但是long不能自动转换为int,相当于领导是人民,
//但人民可能不是领导
引用 是占的同一个空间,而且是直接指向关系,改变一个,另一个也会改变,
指针是间接指向,改变一个,也会改变另一个的值,但是占用更多的空间
一个对象用于构造另外一个对象,如果用于构造另外一个对象的对象有指针等与堆栈相关的变量,则会出现堆栈空间重合,此时需要单独的
一个拷贝构造函数,这个构造函数需要实现开辟新的堆栈空间的功能,如用new关键字开开辟新的空间,new的返回值为一个地址值
自加重载运算符中的前+要用引用返回,后加要用值返回,自减类似,前加的返回类型应该为该类的类型,一般为*this,后加的返回类型同
样为对象的类型,但后加应该为返回临时对象
重载中,+ - += -=等双目运算应该返回值,而++ -- !~ ^ 等单目运算一般返回引用,但后减与后加返回值
一个函数声明为引用,那么它返回时不会产生临时变量
一个变量声明为引用,那么它会直接指向赋值给它的那个变量的空间
如果:
int& fun();
int &a=fun();
那么a就直接指向了fun函数中某个返回值的全局变量,且a与fun()函数本身的直接改变,如a++,fun()++,都会影响引用的全局变量
如果fun没有声明为引用,a仍然为引用,即下面的形式:
int fun();
int& a=fun();
那么fun函数会首先将值保存到一个临时变量,这个临时变量是与返回的变量不同的一个,而a直接指向这个临时变量,a是那个临时变量的别
名,此时左值会影响右值(临时变量的值),但是右值不会影响左值
如果声明为下面的形式:
int fun();
int a=fun();
那么fun函数会首先将值保存到堆栈空间的临时变量,再由这个临时变量将值拷贝到a中
如果是下面这种:
int& fun();
int a=fun();
那么fun函数会直接将值拷贝到a中,不会产生临时变量,并且fun()函数中返回的变量应该是一个全局变量,相当于返回的是全局变量的别
名,如果fun()进行自加:fun()++则会使得全局变量也自加,所以返回的必须是全局变量
拷贝构造函数:
ClassName::ClassName(ClassName& Temp) //这里不应该直接改变Temp对象变量的值,会影响用于构造新对象的那个对象
{
//code
}
用一个对象构造另外一个对象,一般来说,他们的值一样,但空间位置不一样
但是如果对象中有指针变量,则两个对象的指针指向同一个空间,即指针值相同,但是,指针本身所在的空间也应该是不同的
friend 关键字用于声明友元函数或者类,友元函数或类可以访问该类的一切成员,包
函数:
friend int fun();
类:
friend class ClassName;
float& ClassArray::operator[](int index)
{
if index是否越界;
//code
return 对象中的数组的下标为index的元素;
}
float& ClassArray::operator()(int x,int y) //这个运算符还可以用于重载函数,关键看它的code实现的功能和返回值
{
if index是否越界;
//code
return 对象中的二维数组的下标为array[x][y]的元素;
}
const int a=40; // a可以定义为int a=40;
const int &b=a;//
int const &b=a;//同上,这与const和指针的关系不同
也可以是下面这样:
const int &b=40;
左值引用&表示左边的变量直接指向右边的数据或者变量的空间,也可以说是它的别名,左值引用,修改左边的变量,就等于修改右边的变
量
返回引用的函数,它是一个复制数据的过程,不同于左值引用,左值引用是直接指向右边数据的空间,
如果定义const int a=40;那么只能定义const int& b=a;//b的前面必须加const,因为a是一个恒值,如果不加,以为着a可能要改变,所以
编译通不过,因为引用,改变b就是改变a,它们占用同一空间
1、指向常量的指针,即*p为一常量,不能改变,而p可以改变
const int a=40;
int a=40;//皆可
const int *p=&a;//p指向一个整形常量,这里*p为40,但是p可以另外赋值,如p=&b,但是*p仍然为40,但实际上对它赋值数据可以不是常
量
int const *p=&a;//同上
2、指针常量,即*p可以改变,但是p不能改变,指针本身是一个常量
int * const p=&a;//p 是一个常量指针,它指向一个整形变量
3、指向常量的指针常量
const int a=40;
int a=40;//也可以
const int * const p=&a;//a可以为一个常量或者一个变量,着重强调的是p和*p为常量,并不是对它赋值的变量
对于int a[2][3]={1,2,3,4,5,6};
a代表二维数组第一行的地址,是行首的地址,它是一个行常量指针,而a[0]相当于a[0]+0,指向第一行第一列元素的地址,相当于*(a+0),
可以直接替换,即第一行首地址a中存放了第一行元素的首地址,而a[0]存放第一行第一列的首地址
a=*a,但它们的含义不同,a+1为第二行的首地址,而*a+1为第一行第二列的首地址
即有a是指向行的行常量指针,而*a是指向列的常量指针,并且并不存在*a这样一个具体的存储单元,这是一种计算方法,我们不能这样定
义一个:int **p;p=a;这是错误的,因为a当中的元素不是地址
如果有定义:int (*p)[3],则可以有:p=a;即p指向第一行首地地,指向行,p[0]为第一行第一列,p[0][0]为第一行第一列的值,数组名a其
实是一个常量行指针,不能自加自减,p是一个指向拥有三个元素的数组,(*p)[0]=p[0][0],可以表示为*(*(p+i)+j),即p[i][j]
*(p[0]+1)为2,(*p+1)[1]为3,(*(p+1))[1]为5
a不是指向指针的指针
char *a[]={"111","2222"};//多维字符串的定义方法,但二维数组必须指定列的长度,这里a是一个指针数组,元素是地址
即a[0]="111";是一个地址
char **p;//指向指针的指针,含义就是:p里面存入的是指向char型指针(地址)的指针(地址)
此时可以有:p=a;即有*p=a[0],还是一个地址,此时会有p与*p会不同的值,即它们指向不同的地址,**p为字符1如果以char型输出,则为
1,如果以int型输出,输出的则为字符1的ASCII码
const 常函数:
double function()(double x,double y) const; //常函数一般定义方法,常函数不能对数据成员进行修改,否则会报错
double ClassName::function(double x,double y) const //一般格式
{
return a;
}
返回值为const的函数
const int function(); //此时,说明,返回值一个整形常量,这个常量是不能被修改的,对指针或者引用有用,准确是说是:
If you are returning an object of a user-defined type by value as a const, it means the returned value cannot be modified.
If you are passing and returning addresses, const is a promise that the destination of the address will not be changed.
指针函数:返回一个地址值
int *p(int x,int y)
{
int *temp;
//code
return temp;
}
指向函数的指针:
int (*p)(int,int);//这个函数只需要在开头声明即可,不需要具体实现
使用时:如果有一个函数为:int max(int x,int y)
则可以这样调用max函数:
p=max;
temp=(*p)(a,b);//a、b为参数
int& fun() //引用返回时,不会创建临时变量,而是把值直接复制给变量或者常量:a=fun();
{
int temp;
//code
reutn temp;
}
const int& fun() //同理是将返回的值作为一个常量,直接复制值给一个常量或者变量:a=fun();a可以是常量或者变量,
//而对返回的值赋值的那个数据可以不是常量,可以是任意的变量,如变量temp;
//int a=fun();则左值a直接从右边的返回值中复制数据,如果是int& a=fun();则a直接指向返回值的地址
{
int temp;
//code
return temp;
}
const int *fun() //返回指向常量的指针比较特殊,const int *p=fun();此时因为fun返回的是一个指针,而且它指向一个常量,所以指
//针所指向的那个值不能修改,因此,*p不能修改值,因此前面必须要用const限定
{
int *p;
//code
return p;
}
int *const fun() //返回的仍然是一个指针,但指针是一个常量,只能这么调用:int *const p=fun();
main函数的参数:
int main(int argc,char **argv) //其中,argc表示从标准输入中得到了几个参数,如果有文件名,包含文件名
//argv[0]为文件名
{
//CODE
return 0;
}
要使用上面这个,需要找到工程文件编译生成的可执行文件,进入可执行文件所在的文件夹,直接输入文件名,即可运行,文件名后即为参
数,参数以空格分隔,如果参数本身有格,以“”双引号括起来,“”中的内容为一个参数
scanf("%d,%d");// c语言可以这样分隔,且输入函数中不能有换行符,但是c++不可以
c++一般用:
cin>>a>>b; //ab为变量
C++的<string>库中有一个getline()函数
用法是
istream& getline(istream& is,string& str,char delimiter='\n')
第一个参数是输入流,
第二个参数是字符串变量,
第三个参数是分隔符,默认分隔符是'\n'。
分隔符只能是半角字符,
如果要用','
那么就是
getline(cin, str_, ',');
完整的程序写出来就是
#include <string>
#include <iostream>
using namespace std;
int main()
{
string s;
getline(cin, s, ',');
//..............
//.............
}
如果输入是:
56,23
那么,s中的内容就是"56"
这样,再把字符串转成数字就不困难了
p=fopen("f:\11.txt","r+");
if(p==NULL)
char array[20];
memset(array,0,20);
函数模板:
template<typename T,typenaem R> //其中,typename可以用 class关键字代替,其中T一但定义,可以在函数中当作类名使用,即可以定
//义变量,T、R必须在函数参数列表中至少出现一次,如这里的swap1函数中,否则无用,
void swap1(T& a,T& b,R& c){ //void也可以换成T,因为它就是一个类型
T temp=a;
a=b;
b=temp;
}
int main()
{
double x=3.3,y=4.5;
int a=1,b=4,c=0;
swap1(x,y,c);
swap1(a,b,c);
return 0;
}
头文件是不会被编译的,这个变量会在所有 include 这个头文件的 cpp 文件里定义,这样这个变量就被重复定义了。正常的方法是在头文
件里用 extern 声明,然后找个 cpp 定义。extern int i; //hint i = 5; //cpp
在C和C++中,如果一定要访问全局变量该怎么实现?
#include <iostream>
using namespace std;
int global= 5;
//这个是全局变量int main(){
if(1 == 1)
{
int global= 4; // 这个是局部变量
cout << global <<endl; //输出局部变量
cout << ::global <<endl;//输出全局变量,使用作用域标识符 ::
} return 0;
}//当::的前面没有任何对象时,它代表后面的对象或函数是全局的。
分内分配:
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(heap):由malloc,new等分配的空间的地址,地址由低向高增长(程序员释放)。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配
方式倒是类似于链表。
栈(stack):是自动分配变量,以及函数调用所使用的一些空间(所谓的局部变量),地址由高向低减少;
3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化
的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。-->分别是data区,bbs区
4、文字常量区 — 常量字符串就是放在这里的,程序结束后由系统释放 。-->coment区
5、程序代码区 — 存放函数体的二进制代码。-->code区
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0;// 全局初始化区
char *p1; //全局未初始化区
main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; 栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c =0;// 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配 得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
静态数据与函数:
1、使用前声明要加static,但定义时都不加static,但都要有返回类型(静态成员函数)或者变量的数据类型(静态数据),
如果在定义函数时,在前面加了static,表示这个函数只在本文件中使用,并不表示它是类的静态函数
2、必须在类外初使化,不能在构造函数中,因为它属于类,初使化形式:<数据类型> <类名> ::<静态数据成员名>=<值>
且初使化时不能加访问权限运算符,如public等,一般的编译器只能声明为public的访问权限
3、定义时使用访问权限运算符表明对它的访问权限,这点与类中的普通变量一致,但初使化必须在类外,即定义类之后必须对它使化,否
则 自动初使化为0
4、静态数据和全局变量都是存放在静态区
5、静态函数只能访问静态数据成员
6、静态成员与函数没有this指针,因为它们属于类,不属于对象,只能使用<类名> ::<静态数据成员名>方法引用
7、其作用一般是用于计数,因为它属于类
extern "C"用于封装自己的C语言函数,extern "C"即表示下面的代码用c代码编写,有固定格式
比如qt中的close()函数,部件中有,C语言中打开文件函数中也有,因此,直接使用close()函数,编译无法完成,只能将c语言中close()
函数封装到一个函数中,从而在C++中调用这个函数
时常在cpp的代码之中看到这样的代码:
#ifdef __cplusplus
extern "C" { //extern "C"即表示下面的代码用c代码编写,比如return这个函数是c中的代码,不是c++中的代码
#endif
//一段代码
#ifdef __cplusplus
}
#endif
这样的代码到底是什么意思呢?首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上
面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。
要明白为何使用extern "C",还得从cpp中对函数的重载处理开始说起。在c++中,为了支持重载机制,在编译生成的汇编码中,要对函
数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++和C对产生
的函数名字的处理是不一样的.
这里还有一个重点,函数默认都为extern型,这是一种访问类型,所以在c++的类中的函数都没有加extern,但是如果多个文件要共享一个全
局变量,则要加extern声明那个变量,例如:extern int a;这里的a已经在另外的文件中定义并初使化,如果是数组,则extern int a[];
定义初使化时不要加extern
为何要使用extern “C”
明白了加入与不加入extern "C"之后对函数名称产生的影响,我们继续我们的讨论:为什么需要使用extern "C"呢?C++之父在设计C++之时
,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而 extern "C"就是其中的一个
策略。
试想这样的情况:一个库文件已经用C写好了而且运行得很良好,这个时候我们需要使用这个库文件,但是我们需要使用C++来写这个新的代
码。如果这个代码使用的是C++的方式链接这个C库文件的话,那么就会出现链接错误.
我们来看一段代码:首先,我们使用C的处理方式来写一个函数,也就是说假设这个函数当时是用C写成的:
//f1.c
extern "C" //下面的代码用c代码编写,比如return这个函数是c中的代码,不是c++中的代码
{
void f1()
{
return;
}
}
编译命令是:gcc -c f1.c -o f1.o 产生了一个叫f1.o的库文件。再写一段代码调用这个f1函数:
// test.cxx
//这个extern表示f1函数在别的地方定义,这样可以通过
//编译,但是链接的时候还是需要
//链接上原来的库文件.
extern void f1();
int main()
{
f1();
return 0;
}
通过gcc -c test.cxx -o test.o 产生一个叫test.o的文件。然后,我们使用gcc test.o f1.o来链接两个文件,可是出错了,错误的
提示是:
test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()'
也就是说,在编译test.cxx的时候编译器是使用C++的方式来处理f1()函数的,但是实际上链接的库文件却是用C的方式来处理函数的,
所以就会出现链接过不去的错误:因为链接器找不到函数。
因此,为了在C++代码中调用用C写成的库文件,就需要用extern "C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它
们。
比如,现在我们有了一个C库文件,它的头文件是f.h,产生的lib文件是f.lib,那么我们如果要在C++中使用这个库文件,我们需要这
样写:
extern "C"
{
#include "f.h"//f.h中
}
回到上面的问题,如果要改正链接错误,我们需要这样子改写test.cxx:
extern "C"
{
extern void f1();//这里表示f1()函数在某个函数中有具体定义
}
int main()
{
f1();
return 0;
}
重新编译并且链接就可以过去了.
指向函数的指针:
int fun(int i)
{
return i;
}
int (*p)(int);
那么对于fun的调用可以有两种方法:1、p=fun;(*p)(4);或者2、p=fun;p(4);
即p(4)与(*p)(4)等效
enum类型的成员,默认情况下是从0开始赋值;
如果中间某个成员进行了手动赋值的话,该成员的下一个成员的值为该成员的值+1
typedef enum {
PLAYMODE = 1, // 默认从0开始
PAUSEMODE, //值为2
STOPMODE = 5,
TRACKRPTMODE, //值为6
MIXMODE //值为7
}Status_t;
这样可以用Status_t去定义变量
也可以直接定义变量:
enum Status_t{
PLAYMODE = 1, // 默认从0开始
PAUSEMODE, //值为2
STOPMODE = 5,
TRACKRPTMODE, //值为6
MIXMODE //值为7
}; //此时Status_t为一变量
关于可重入代码
简单介绍,因为驱动可以被多个进程调用,互不干扰,这样驱动必须是可重入的。
可重入最简单的理解就是所有变量都是局部变量。
当对字符串采用逐个赋值的时候,必须要在末尾赋值'\0',否则可能出现乱码,采用strcpy会自动赋值'\0'
sprintf 是个变参函数,定义如下:
int sprintf( char *buffer, const char *format [, argument] ... );
除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数: 格式化字符串上。 printf 和
sprintf 都使用格式化字符串来指定串的格式,在格式串内部使用一些以“%”开头的格式说明符(format specifications)来占据一个位
置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要的字符串。
如:
//把整数123 打印成一个字符串保存在s中。
sprintf(s, "%d", 123); //产生"123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%4d %d",123, 4567); //产生:" 123 4567" 当然也可以左对齐
一个函数中,如果有指针参数,且有关于指针参数的长度限制,那么指针参数必须是一个已经定义了的空间,如用malloc或全局变量空间地
址,这个函数本身不再开辟空间,eg: int fun(char *p,int p_len);
反过来如果一个函数中仅用了指针,没有关于指针的长度参数,那么指针值必然可以用这个局函数获得,这个局部函数中可能有malloc等函
数,eg: int fun(char *p);但如果是int fun(const char *p);表明不会修改p指向的内容,那么一般p也是个已经定义的空间首地址
如果有void fun(int **p);那么p可以没有被初使化,给它传递时应为:int *pp;fun(&pp);此时如果在函数中使用*p=(int *)malloc(),可
以直接修改pp指向的内存空间位置,也可以用**pp修改间接指向的值
enum与struct后接的都是类型名,C++一旦定义好类型的成员,那么可以直接用类型名声明变量。enum只占四个字节
enum Week{Mon,Tue,Wed,Thu,Fri,Sat,Sun};
下面用类名定义变量:
Week day;
day=Mon;//此时,day=0;
day不能这样:day=0;但可以day=(Week)0;//强制类型转换
重点:day是一个变量,Week是一个类名,但是day取值只能是上面的七个中的一个,且那七个并不是day的成员,从Mon到Sun都是单独的常
量,其含义是Week类中包含了七个常量,这七个常量的值可以改变,但类所定义的变量只能取其七个值中的一个,那七个值可以单独的使用
,没有day.Mon的用法,Mon不是一个变量成员,它是一个常量
typedef struct {
enum {Mon=10,Tue,Wed,Thu,Fri,Sat,Sun}b;//占用空间4字节
}A;
A a;
cout<<a.Mon<<endl;//输出10
a.b=a.Mon;//把常量赋值给变量b
cout<<a.b<<endl;//输出10
另一种使用方法:
class A{
public:
enum B{Mon=10,Tue,Wed,Thu,Fri,Sat,Sun};
//protected:
static int m;//部分编译器只支持静态数据成员声明为静态变量,其继承与访问权限与普通变量一致
};
int A::m=0; //静态数据必须在类外初使化
可以这样引用:cout<<A::Mon;//输出为10,相当于静态变量
A::B b;//重点,这就是C++封装常量的方法
b=A::Mon;//b等于10
类中的类:
class A{
public:
int a;
class B{
public:
int m;
};
};
A::B b;
b.m=1;
仍然可以看成是两个单独的类,只是内部那个的初使化要加A::B引导
构造函数中也可以用return;返回一个空值
函数模板:
template <typename 类型参数1,typename 类型参数2> //typename比class关键字更好
返回类型 函数模板名(数据参娄列表)
{
//函数实现
}
eg:
template<typename T>
void swap(T& a,T& b)
{
T temp=a;a=b;b=temp;
}
说明:
函数模板将数据类型参数化,这使得在程序中能够用不同类型的参数调用同一个函数(模板函数)。在调用模板函数时即创建函数模板的一
个实例,这个过程称为函数模板的实例化。
函数模板的实例化由编译器完成:编译时函数模板本身并不产生可执行代码,只有在函数模板被实例化时,编译器才按照实参的数据类型进
行类型参数的替代,生成新的函数(模板函数)。
类模板:
为了起到模板的作用,与函数模板一样,定义一个类模板时必须将某些数据类型作为类模板的类型参数。
模板类的实现代码与普通类没有本质上的区别,只是在定义其成员时要用到类模板的类型参数。
一般定义:
template < typename T >
class MyTemClass
{
private:
T x;
// 类型参数T用于声明数据成员
public:
void SetX( T a ) { x=a; };
// 类型参数T用于声明成员函数的参数
T GetX( ) { return x; };
// 类型参数T用于声明成员函数的返回值
};
如果在模板类的外部定义模板类的成员函数,必须采用如下形式:
template < typename T > // 除了同样要包含类的定义声明以外,还要在每个函数模板前面声明这一句
void MyTemClass < T > :: SetX( T a )
{
x=a;
}
MyTemClass<int *> my;//my仍然是一个对象,只是相应的函数的反回值和参数发生变化,T变为int *,那么对于GetX()的调用,返回的是一
个指针,但是对它的调用形势仍然是:对象.成员,即my.GetX()。但要用它的返回值来调用另外的函数,那么可能会发生变化,因为它返回
的是一个指针。
与函数模板不同,类模板不是通过调用函数时实参的数据类型来确定类型参数具体所代表的类型,而是通过在使用模板类声明对象时所给出
的实际数据类型确定类型参数。
例如,以下使用类模板声明了一个类型参数为int的模板类的对象:
MyTemClass < int > intObject;
对于上面的对象声明,编译器首先用int替代模板类定义中的类型参数T,生成一个所有数据类型已确定的类class,即类模板的实例化,产生
一个具体的类;instantiate:实例化
然后再利用这个类创建对象intObject。
含有多个类模板参数的定义:
template < class T1,int i,class T2 >
class MyTemClass
{ . . . }
则声明模板类的对象应采用如下形式:
MyTemClass< int, 100, float > MyObject ;
MyTemClass< int, 100, float >仍然是实例化产生一个类,即一个模板类,它由类模板产生。
重点:不管是函数模板还是类模板,如果关键字类型名T为指针型,那么相应的模板函数或模板类都必须用指针,特别是模板类中的函数如
果有的是以T为参数的,那么调用时,T后所要传递的参数必定也为指针。这是相对应的。
前加与后加:
myclass&operator ++()//前缀++,对自身进行了操作,返回引用,表示它对自身进行了操作,但并不影响左值,
{ //如果左值要能对右值所批向的对象(全局变量)进行操作,左值必须是引用(即用别名),
num++;
return *this; //this是一个全局指针变量
}
myclass operator++(int i)//后缀++,返回的是一个局变量,表示先使用它本身,由于是局部变量,因此不能返回引用
{
myclass a = *this;
num++;
return a;//不能返回局部变量的引用。
}
C++类型转换总结
C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是:
TYPE b = (TYPE)a。
C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。
const_cast,字面上理解就是去const属性。
static_cast,命名上理解是静态类型转换。如int转换成char。
dynamic_cast,命名上理解是动态类型转换。如子类和父类之间的多态类型转换。
reinterpret_cast,仅仅重新解释类型,但没有进行二进制的转换。
4种类型转换的格式,如:TYPE B = static_cast(TYPE)(a)。
const_cast
去掉类型的const或volatile属性。
1 struct SA {
2 int i;
3 };
4 const SA ra;
5 //ra.i = 10; //直接修改const类型,编译错误
6 SA &rb = const_cast<SA&>(ra);
7 rb.i = 10;
static_cast
类似于C风格的强制转换。无条件转换,静态类型转换。用于:
1. 基类和子类之间转换:其中子类指针转换成父类指针是安全的;但父类指针转换成子类指针是不安全的。(基类和子类之间的动态类型转
换建议用dynamic_cast)
2. 基本数据类型转换。enum, struct, int, char, float等。static_cast不能进行无关类型(如非基类和子类)指针之间的转换。
3. 把空指针转换成目标类型的空指针。
4. 把任何类型的表达式转换成void类型。
5. static_cast不能去掉类型的const、volitale属性(用const_cast)。
1 int n = 6;
2 double d = static_cast<double>(n); // 基本类型转换
3 int *pn = &n;
4 double *d = static_cast<double *>(&n) //无关类型指针转换,编译错误
5 void *p = static_cast<void *>(pn); //任意类型转换成void类型
dynamic_cast
有条件转换,动态类型转换,运行时类型安全检查(转换失败返回NULL):
1. 安全的基类和子类之间转换。
2. 必须要有虚函数。
3. 相同基类不同子类之间的交叉转换。但结果是NULL。
1 class BaseClass {
2 public:
3 int m_iNum;
4 virtual void foo(){}; //基类必须有虚函数。保持多台特性才能使用dynamic_cast
5 };
6
7 class DerivedClass: public BaseClass {
8 public:
9 char *m_szName[100];
10 void bar(){};
11 };
12
13 BaseClass* pb = new DerivedClass();
14 DerivedClass *pd1 = static_cast<DerivedClass *>(pb); //子类->父类,静态类型转换,正确但不推荐
15 DerivedClass *pd2 = dynamic_cast<DerivedClass *>(pb); //子类->父类,动态类型转换,正确
16
17 BaseClass* pb2 = new BaseClass();
18 DerivedClass *pd21 = static_cast<DerivedClass *>(pb2); //父类->子类,静态类型转换,危险!访问子类m_szName成员越界
19 DerivedClass *pd22 = dynamic_cast<DerivedClass *>(pb2); //父类->子类,动态类型转换,安全的。结果是NULL
void copy(char **str)
{
*str="mmmm";
}
如果有:
char *s="my";
copy(&s);
其结果是:s指向"mmmm",会给mmmm分配单独的空间,s不再指向"my"
void copy(string str)
{
str="mmmm";
}
如果有:
string s="my";
copy(s);
其结果是浅拷贝,s仍然是"my"
重点:remove:这样的代码只是移除数据,特别是new malloc开辟的空间数据,并没有从内存中清除,delete直接清除
remove相当于从链表中移出去,但是这个数据本身仍然在
大小端问题:这个只对于大数据类型有关,如long,结构体,char型数据不受影响,因为它本身仅有一个数据
网络中一般是大端。当然,一个数据(&变量)本身所代表的地址应该都是低地址。
比如:long a=0x12345678;// 32位数据
大端:内存中的高地址存放低字节,低地址存放高字节,则内存由低到高地址为:0x12,34,56,0x78
小端:则内存由低到高地址为:0x78,56,34,0x12
如果:char *p=(char *)&a; //取a在内存中的最低地址的8位数据
*p=0x12,则为大端,反之小端
*p=0x78小端
一个刚定义的类,能在类中定义返回它自身类型的指针函数,但是不能定义返回自身类型的对象,因为它没有定义完全。