函数指针
程序运行期间,每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址(入口地址)。我们可以将函数的入口地址赋给一个指针变量,使该变量指向该函数。然后通过指针变量就可以调用这个函数。这种指向函数的指针变量称为函数指针。
#include <iostream>
#include <vector>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <fstream>
#include <cstddef>
void print_smaller(int a,int b)
{
std::cout << ((a < b) ? a:b )<< std::endl;
}
int main()
{
/*等价于 void (*fn)(int ,int ) = print_smaller;*/
void (*fn)(int ,int );
fn = print_smaller;
fn(3,4);
return 0;
}
std::endl与"\n"区别
cout << endl : Inserts a new line and flushes the stream
cout << "\n" : Only inserts a new line.
As a conclusion, cout << “\n” seems performance wise better than cout << endl; unless flushing of stream is required.
命令行参数
#include <stdio.h>
int main(int argc,char *argv[])
{
for(int i = 0;i < argc;i++)
printf("%s\n",argv[i]);
return 0;
}
命令行参数这部分有个知识点就是说如果参数中带有空格,可以使用双引号括起来。
例如./example hello world "hello world" 程序输出
./example
hello
world
hello world
右移运算符
实际上右移n位就相当于左操作数除以2的n次方,并且将结果往小里取整,例如:
-25 >> 4 = -2
-2 >> 4 = -1
18 >> 4 = 1
对于有符号数,如long,int,short,char类型变量,在右移时,符号位(即最高位)将一起移动,并且大多数C/C++编译器规定,如果原符号位为1,则右移时高位就补充1,原符号为0,则右移时高位就补充0。
引用作为函数的返回值
int n = 4;
int & SetVaule(){return n;}
int main()
{
SetValue() = 40;
cout << n;
return 0;
}//输出40
常引用
不能通过常引用去修改其引用的内容。
int n = 100;
const int & r = n;
r = 200;//编译报错
n = 300;//没问题,不能通过常引用去修改其引用的内容,但没有限制通过其它方式修改n的内容
常量指针
不能把常量指针赋值给非常量指针。
可以把非常量指针赋值给常量指针。
原因如下:
定义常量指针的初衷是不能通过常量指针修改其指向的内容。如果把常量指针赋值给非常量指针,就可以通过非常量指针修改常量指针指向的内容。
函数参数为常量指针时,可避免函数内部不小心改变参数指针所指地方的内容
void MyPrintf(const char *p)
{
//strcpy函数原形第一个参数为char*,由于不能将常量指针赋值给非常量指针,因此编译报错
strcpy(p,"this");//编译出错
printf("%s",p);
}
用new运算符实现动态内存分配
new T;
new T[n];
这两个表达式返回值的类型都是T*
内联函数
函数调用是有开销的。如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。
为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
在函数定义前面加inline关键字,即可定义内联函数。
函数重载
一个或多个函数,名字相同,然而参数个数或者参数类型不相同,这叫做函数的重载。函数名字相同,参数相同,仅返回值不同,不是函数重载,而是重复定义。
好处:函数重载使得函数命名变得简单。
编译器根据调用语句中的实参的个数和类型判断应该调用哪个函数。
函数缺省参数
C++中定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。
函数参数可缺省的目的在于提高程序的可扩充性。
如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。
对象的内存分配
对象的内存空间
对象的大小=所有成员变量的大小之和
一个对象的某个成员变量被改变,不会影响到其他的对象。
对象之间可以用=进行赋值,不能使用
== != > < >= <=
进行比较,除非这些运算符经过了重载。
#include <iostream>
using namespace std;
class CRectangle{
public:
int w;
int h;
void Init(int w_,int h_);
int Area();
int Perimeter();
};
void CRectangle::Init(int w_,int h_){
w = w_;
h = h_;
}
int CRectangle::Area(){
return w*h;
}
int CRectangle::Perimeter(){
return 2*(w + h);
}
int main()
{
int w,h;
CRectangle r;
cin>>w>>h;
r.Init(w,h);
cout<<r.Area()<<endl<<r.Perimeter()<<endl;
return 0;
}
类成员的可访问范围
关键字--类成员可被访问的范围
private:指定私有成员,只能在成员函数内被访问,设置私有成员的目的是强制对成员变量的的访问一定要通过成员函数进行。
public:指定公有成员,可以在任何地方被访问
protected:指定保护成员
三种关键字出现的次数和先后顺序都没有限制
缺省为私有成员
例如:
class Man{
int nAge;//私有成员
char szName[20];//私有成员
public:
void SetName(char *Name){
strcpy(szName,Name);
}
};
内联函数
定义方法:
inline + 成员函数
整个函数体出现在类定义内部
例如:
class B{
inline void func1();
void func2()
{
}
};
void B::func2()
{
}
成员函数重载
#include <iostream>
using namespace std;
class Location{
private:
int x;
int y;
public:
void init(int x = 0,int y = 0);
void valueX(int val)
{
x = val;
}
int valueX()
{
return x;
}
};
void Location::init(int X, int Y)
{
x = X;
y = Y;
}
int main()
{
Location A;
A.init(5);
A.valueX(5);
std::cout<<A.valueX()<<std::endl;
return 0;
}
使用缺省参数要注意避免有函数重载时的二义性,例如将上面代码中的valueX函数更改为下面的实现,编译报错。
void valueX(int val = 0){x = val;}
Location A;
A.valueX()//编译器无法判断调用哪个valueX
报错信息如下:
main.cpp:33:18: error: call to member function 'valueX' is ambiguous
构造函数
成员函数的一种,名字与类名相同,可以有参数,不能有返回值(void也不可以)
作用是对对象进行初始化,如给成员变量赋初值
如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数,默认构造函数无参数,不进行任何操作。
如果定义了构造函数,则编译器不生成默认的无参数的构造函数
对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数。
一个类可以有多个构造函数。
例子:
class Complex{
private:
double real,image;
public:
void Set(double r,double i);
};//编译器自动生成默认构造函数
Complex c1;//默认构造函数被调用
Complex * pc = new Complex;//默认构造函数被调用
class Complex{
private:
double real,image;
public:
Complex(double r,double i = 0);
};
Complex::Complex(double r,double i){
real = r;
image = i;
}
Complex c1;//error,缺少构造函数的参数
Complex * pc = new Complex;//error,没有参数
Complex c1(2);//OK
Complex c1(2,4),c2(3,5);
Complex * pc = new Complex(3,4);
可以有多个构造函数,参数个数或类型不同
class Complex{
private:
double real;
double image;
pubic:
void Set(double r,double i);
Complex(double r,double i);
Complex(double r);
Complex(Complex c1,Complex c2);
};
Complex::Complex(double r,double i)
{
real = r;
image = i;
}
Complex::Complex(double r)
{
real = r;
image = 0;
}
Complex::Complex(Complex c1,Complex c2)
{
real = c1.real + c2.real;
image = c1.imag + c2.imag;
}
Complex c1(3),c2(1,0),c3(c1,c2);//c1 = {3,0},c2 = {1,0},c3 = {4,0};
构造函数在数组中的使用
#include <iostream>
using namespace std;
/*构造函数在数组中的使用*/
class CSample{
int x;
public:
CSample(){
cout << "Constructor 1 Called"<<endl;
}
CSample(int n){
x = n;
cout <<"Constructor 2 Called"<<endl;
}
};
int main()
{
CSample array1[2];
cout << "step1"<<endl;
CSample array2[2] = {4,5};
cout << "step2"<<endl;
CSample array3[2] = {3};
cout << "step3"<<endl;
CSample * array4 = new CSample[2];
delete []array4;
return 0;
}
程序输出:
Constructor 1 Called
Constructor 1 Called
step1
Constructor 2 Called
Constructor 2 Called
step2
Constructor 2 Called
Constructor 1 Called
step3
Constructor 1 Called
Constructor 1 Called
复制构造函数
只有一个参数,即对同类对象的引用。
形如 X::X(X &)或 X::X(const X &),二者选一,后者能以常量对象作为参数。
和构造函数一样,如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
class Complex{
private:
double real,imag;
};
Complex c1;//调用缺省无参构造函数
Complex c2(c1);//调用缺省的复制构造函数,将c2初始化为c1
如果自己定义复制构造函数,则默认的复制构造函数不存在。
#include <iostream>
using namespace std;
class Complex{
public:
double real;
double imag;
Complex(double r,double i)
{
real = r;
imag = i;
}
Complex(const Complex & c)
{
real = c.real;
imag = c.imag;
}
};
int main()
{
const Complex c1(3.5,4.5);
Complex c2(c1);
cout << c2.real << " "<< c2.imag << endl;
return 0;
}
上面代码中复制构造函数参数有const限定,如果不加const的话将无法使用const成员初始化。
复制构造函数参数应为本类的引用,不允许有形如X::X(X)的构造函数。
例如:
class CSample{
/*不允许存在这样的构造函数*/
CSample(CSample c){
}
}
复制构造函数应用
1)当一个对象去初始化同类的另一个对象时
Complex c2(c1);
Complex c2 = c1;//初始化语句,非赋值语句
2)如果某函数的参数为类A的对象,那么该函数调用时必然会给参数赋值,此时类A的复制构造函数将被调用。
#include <iostream>
using namespace std;
class Complex{
public:
double real;
double imag;
Complex(double r,double i)
{
real = r;
imag = i;
}
Complex(const Complex & c)
{
real = c.real;
imag = c.imag;
cout << "copy constructor called."<<endl;
}
};
int AddComplexReal(const Complex c1,const Complex c2)
{
return c1.real + c2.real;
}
int main()
{
const Complex c1(3.5,4.5);
const Complex c2(6.5,5.5);
cout << AddComplexReal(c1,c2) << endl;
return 0;
}
上面的例子中函数AddComplexReal的两个成员时Complex类,调用函数时会将参数由实参赋值给形参,因此会调用复制构造函数。
3)如果函数的返回值是类A的对象,则函数返回时,A类的复制构造函数被调用。
#include <iostream>
using namespace std;
class A
{
public:
int v;
A(int n)
{
v = n;
}
A(const A & a)
{
v = a.v;
cout <<"Copy constructor called."<<endl;
}
};
A Func()
{
A b(4);
return b;
}
int main()
{
cout << Func().v << endl;
return 0;
}
我们编译时直接使用g++ example.cpp,运行./a.out没有看到输出Copy constructor called.
如果我们添加参数-fno-elide-constructors,然后生成./a.out就可以看到有输出Copy constructor called.
原因如下:
-fno-elide-constructors
The C++ standard allows an implementation to omit creating a temporary that is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.
原来是编译器优化掉了创建临时对象,因而少了一次复制构造函数,可以使用选项-fno-elide-constructors关闭该优化。
类型转换构造函数
实现类型的自动转换,只有一个参数,不是复制构造函数。
#include <iostream>
using namespace std;
class Complex{
public:
double real;
double imag;
Complex(int i){//类型转换构造函数
cout << "IntConstructor called"<<endl;
real = i;
imag = 0;
}
Complex(double r,double i)
{
real = r;
imag = i;
}
};
int main()
{
Complex c1(7,8);
Complex c2 = 12;//=是初始化,不会生成临时Complex对象
c1 = 9;//9被自动转换成一个临时的Complex对象
cout << c1.real <<","<<c1.imag << endl;
return 0;
}
析构函数
析构函数在函数名前加~,没有参数和返回值,一个类最多只有一个析构函数。
对象消亡时自动调用析构函数,在对象消亡前做善后工作,比如释放分配的空间等。
定义类的时候没写析构函数,则编译器生成缺省析构函数。如果定义了析构函数,则编译器不生成缺省析构函数。
析构函数的例子:
#include <iostream>
using namespace std;
class String{
private:
char *p;
public:
String(){
p = new char[10];
}
~String();
};
String ::~String(){
delete []p;
}
int main()
{
return 0;
}
析构函数和数组
对象数组生命期结束时,对象数组的每个元素都会调用析构函数。
#include <iostream>
using namespace std;
class String{
private:
char *p;
public:
String(){
p = new char[10];
}
~String();
};
String ::~String(){
delete []p;
cout <<"Helloworld"<<endl;
}
int main()
{
String s[1024];
return 0;
}
函数执行后会输出1024个Helloworld,因为String对象对组有1024个对象,每个对象在生命周期结束后都会调用析构函数。
delete运算导致析构函数调用
String *pstr;
pstr = new String; //构造函数调用
delete pstr;//析构函数调用
pstr = new String[1024];//构造函数调用1024次
delete [] pstr;//析构函数调用1024次
#include <iostream>
using namespace std;
class Demo{
int id;
public:
Demo(int i)
{
id = i;
cout << "id = "<<id<<" Constructed"<<endl;
}
~Demo()
{
cout << "id = "<<id<<" Destructed"<<endl;
}
};
Demo d1(1);
void Func()
{
static Demo d2(2);
Demo d3(3);
cout<<"Func"<<endl;
}
int main()
{
Demo d4(4);
d4 = 6;
cout <<"main"<< endl;
{
Demo d5(5);
}
Func();
cout << "main ends"<<endl;
return 0;
}
程序输出:
id = 1 Constructed
id = 4 Constructed
id = 6 Constructed
id = 6 Destructed
main
id = 5 Constructed
id = 5 Destructed
id = 2 Constructed
id = 3 Constructed
Func
id = 3 Destructed
main ends
id = 6 Destructed
id = 2 Destructed
id = 1 Destructed
先创建的对象后析构,接着是static变量和全局变量。
静态成员
在声明前加static关键字的成员。普通成员变量每个对象有各自的一份,而静态成员变量只有一份,为所有对象共享。sizeof运算符不会计算静态成员变量,例如:
class CMyclass{
int n;
static int s;
}
则sizeof(CMyclass)等于sizeof(int) = 4.
普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象,因此静态成员不需要通过对象就能访问。
如何访问静态成员
class CRectangle{
private:
int w;
int h;
static int nTotalArea;//静态成员变量
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal();//静态成员函数
};
1)类名::成员名
CRectangle::PrintTotal();
2)对象名.成员名
CRectangle r;r.PrintTotal();
3)指针->成员名
CRectangle *p = &r; p->PrintTotal();
4)引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;(public修饰)
静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
静态成员函数本质上是全局函数。
设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
分析如下代码存在什么问题?
class CRectangle{
private:
int w;
int h;
static int nTotalArea;//静态成员变量
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal();//静态成员函数
};
CRectangle::CRectangle(int w_,int h_)
{
w = w_;
h = h_;
nTotalNumber++;
nTotalAre += w*h;
}
CRectangle::~CRectangle()
{
nTotalNumber--;
nTotalArea -= w*h;
}
void CRectangle::PrintTotal()
{
cout<<nTotalNumber <<","<<nTotalArea<<endl;
}
在使用CRectangle类时,有时会调用复制构造函数生成临时的隐藏的CRectangle对象:
调用一个以CRectangle类对象作为参数的函数时,调用一个以CRectangle类对象作为返回值的函数时。
解决方法是增加相应的构造函数:
CRectangle::CRectangle(CRectangle & r)
{
w = r.w;
h = r.h;
nTotalNumber++;
nTotalAre += w * h;
}
成员对象和封闭类
成员对象:一个类的成员变量是另一个类的对象
包含成员对象的类叫封闭类(Enclosing)
#include <iostream>
using namespace std;
class CTyre{
public:
CTyre()
{
cout << "CTyre contructor" << endl;
}
~CTyre()
{
cout << "CTyre destructor" << endl;
}
};
class CEngine{
public:
CEngine()
{
cout << "CEngine contructor"<<endl;
}
~CEngine()
{
cout << "CEngine destructor"<<endl;
}
};
class CCar{
private:
CEngine engine;
CTyre tyre;
public:
CCar()
{
cout <<"CCar contructor"<<endl;
}
~CCar()
{
cout <<"CCar destructor"<<endl;
}
};
int main()
{
CCar car;
return 0;
}
程序执行结果:
CEngine contructor
CTyre contructor
CCar contructor
CCar destructor
CTyre destructor
CEngine destructor
友元
友元函数
例子来自维基百科友元函数.
This approach may be used in friendly function when a function needs to access private data in objects from two different class. This may be accomplished in two similar ways:
a function of global or namespace scope may be declared as friend of both classes
a member function of one class may be declared as friend of another one.
#include <iostream>
using namespace std;
class Foo; // Forward declaration of class Foo in order for example to compile.
class Bar {
private:
int a;
public:
Bar(): a(0) {}
void show(Bar& x, Foo& y);
friend void show(Bar& x, Foo& y); // declaration of global friend
};
class Foo {
private:
int b;
public:
Foo(): b(6) {}
friend void show(Bar& x, Foo& y); // declaration of global friend
friend void Bar::show(Bar& x, Foo& y); // declaration of friend from other class
};
// Definition of a member function of Bar; this member is a friend of Foo
void Bar::show(Bar& x, Foo& y) {
cout << "Show via function member of Bar" << endl;
cout << "Bar::a = " << x.a << endl;
cout << "Foo::b = " << y.b << endl;
}
// Friend for Bar and Foo, definition of global function
void show(Bar& x, Foo& y) {
cout << "Show via global function" << endl;
cout << "Bar::a = " << x.a << endl;
cout << "Foo::b = " << y.b << endl;
}
int main() {
Bar a;
Foo b;
show(a,b);
a.show(a,b);
}
友元类
this指针
一个诡异的例子:
#include <iostream>
using namespace std;
class A
{
int i;
public:
void Hello()
{
cout<<"hello"<<endl;
}
};
int main()
{
A *p = NULL;
p->Hello();
return 0;
}
程序输出hello
c++程序翻译成c程序之后Hello函数为:
void Hello(A * this)
{
cout <<"hello"<<endl;
}
而在调用时即相当于Hello(p)
如果改下Hello函数如下,将导致段错误。
void Hello()
{
cout <<"hello"<<i<<endl;
/*相当于cout <<"hello" <<this->i<<endl;*/
}
常量对象
如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字。
class Demo{
private:
int value;
public:
void SetValue(){}
};
const Demo Obj;//常量对象
常量成员函数
在类的成员函数说明后面可以加const关键字,则该成员函数称为常量成员函数。
常量成员函数执行期间不应该修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。
class Sample
{
public:
int value;
void GetValue() const;
void func(){}
Sample(){}
};
void Sample::GetValue() const
{
value = 0;//wrong
func();//wrong
}
常成员函数的重载
两个成员函数,名字和参数列表都一样,但是一个是const,一个不是,称为重载.
#include <iostream>
using namespace std;
class CTest
{
private:
int n;
public:
CTest()
{
n = 1;
}
int GetValue() const
{
return n;
}
int GetValue()
{
return 2*n;
}
};
int main()
{
const CTest objTest1;
CTest objTest2;
cout<<objTest1.GetValue()<<","<<objTest2.GetValue();
return 0;
}
常引用
对象作为函数的参数时,生成该参数需要调用复制构造函数,效率比较低。为避免该问题,使用对象的引用作为参数。
例如:
class Sample{
...
};
//使用常引用,确保函数不会无意更改o的值
void PrintObj(const Sample & o)
{
...
}
初始化列表
只有构造函数才有初始化列表,语法形式如下:
Point(int i = 0,int j = 0):x(i),y(j) {}//花括号内可以写需要构造函数完成的其它功能
#include<iostream>
using namespace std;
class Point {
private:
int x;
int y;
public:
Point(int i = 0,int j = 0):x(i),y(j) {}
/* The above use of Initializer list is optional as the
constructor can also be written as:
Point(int i = 0, int j = 0) {
x = i;
y = j;
}
*/
int getX() const {return x;}
int getY() const {return y;}
};
int main()
{
Point t1(10, 15);
cout<<"x = "<<t1.getX()<<", ";
cout<<"y = "<<t1.getY();
return 0;
}
常成员初始化必须使用初始化列表
#include<iostream>
using namespace std;
class Test {
const int t;
public:
Test(int t):t(t) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
Test t1(10);
cout<<t1.getT();
return 0;
}
/* OUTPUT:
10
*/
引用成员变量初始化必须使用初始化列表
// Initialization of reference data members
#include<iostream>
using namespace std;
class Test {
int &t;
public:
Test(int &t):t(t) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
int x = 20;
Test t1(x);
cout<<t1.getT()<<endl;
x = 30;
cout<<t1.getT()<<endl;
return 0;
}
/* OUTPUT:
20
30
*/