C语言基础

double

1、C语言中的浮点

假设存在两个浮点变量x和y,精度定义为EPSILON = 1e-6,则错误的比较方式是:

if(x == y)
if(x != y)

应该转换成正确的比较方式:

if(abs(x-y) <= EPSILON) //x等于y
if(abs(x-y) > EPSILON) // x不等于y

同理,x与零值的正确比较方式应该是。

if(abs(x) <= EPSILON) //x等于0
if(abs(x) > EPSILON) //x不等于0
 

2.关于extern "C"

C++中调用C函数:

因为C++编译器会对函数名字进行改编,我们称为name-mangling,为了支持重载,而C函数式用C编译器编译的,显然不是这样的规则,所以加上extern"C"是为了告诉C++编译器按照C编译器的方式来处理这个函数。

extern"C" void func();

#ifdef _cplusplus
extern "C"{
#endif

 ...
  ...
#ifdef _cplusplus
}
#endif


 C中调用C++函数:

C中调用C++中的非成员函数:
//c++ code
extern "C" void func_a();
void func_a()
{
    ....
}
extern void func_a();
int main()
{
    func_a();
    return 0;
}

 

C中调用C++中的成员函数:
class  C
{
    public:
        virtual double f(int);
};

extern "C" double wrapper_f(C* p,int i)
{
   return p->f(i);
}

 

//c code

extern double wrapper_f(void* p,int i);
//extern double wrapper_f(struct C* p,int i);
int main()
{
    wrapper(..,...);
    return 0;
}


3.关于for循环

      对于多维数组来说,正确的遍历方法是要看该语言以什么顺序来安排数组元素的存储空间,比如fortran是以“先列后行”的顺序在内存中连续存放数组元素的,而C/C++则是以“先行后列”的顺序来连续存储数组元素。因此,以“先列后行”顺序存储的数组,就应该用“先列后行”的顺序来遍历,而C/C++中则应该按照“先行后列”的顺序来遍历。

     如果整个数组能够在一个内存页中容纳下,那么在对整个数组进行操作的过程中至少不会因为访问了数组元素而导致缺页中断,页面调度,页面交换等情况,只需要一次外存的读取操作就可以讲数组所在的整个页面调入内存,然后直接访问内存就可以了,此时”先行后列”和“先列后行”的遍历效果差不多(仅有的差别是cache命中率不同)

    但是如果整个数组的内容需要多个内存页来容纳的话,情况就不一样了,尤其是当列数较大的情况下。

    假设一个内存页是4K,int b[128][1024],我们看看先行后列和先列后行都各要多少次页面交换。

//先行后列
for(int j=0;j<128;j++)
   for(int i=0;i<1024;i++)
            b[i][j] = 0;

    外层循环走过一行则内层循环走过改行的1024个元素,正好走完一页,没有发生页面调度,且cache命中率高;当循环进入下一行时,擦配置系统从外存调入下一页。因此可以得知在遍历完整个数组时发生页面调度的次数最多是128次(假设一次调度一页)。

//先列后行
for(int j=0;j<1024;j++)
   for(int i=0;i<128;i++)
          b[i][j]=0;

   在遍历某一列时,由于它包含的每一个元素都恰好位于每一行内,因此内存循环每执行一次就会发生一次页面调度,遍历一列下来就发生128次页面调度,显然总共要发生1024*128次页面调度。

   不过实际情况一般没有这么坏,因为如果物理内存足够的话吗,页面调度和页面交换的次数会减少很多。

   列数大于行数时先行后列的性能优势比较明显。

   关于for循环还有一个优化要说。

//优化前
for(i=0;i<N;i++)
{
    if(condition)
       A();
    else 
       B();
}
//优化后
if(condition)
{
    for(i=0;i<N;i++)
        A();
}
else
{
    for(i=0;i<N;i++)
       B();
}


前者的逻辑判断打断了循环的“流水线”作业,使得编译器不能对循环进行优化处理,较低了效率。

4.#define  const   sizeof

#define

用预处理指令#define声明一个常数,用来表明一年中有多少秒。

#define SECONDS_PER_YEAR (60*60*24*365)UL

写一个标准宏MIN,返回较小的那个值

#define MIN(A,B) ((A)<=(B)?(A):(B))

如果不用括号将参数括起来就会出现意想不到的问题。比如:

#define SQUARE(x) x*x
//语句
a = SQUARE(3+5);
//就会被扩展成
a=3+5*3+5

还有一点要说的是,不要再引用宏定义的参数列表中使用增量和减量运算符,否则可能导致变量多次求值,且结果和预期不符。

int n =5;
int x = SQUARE(n++)

这个结果可能是30,也可能是25。

如果我们不在使用一个宏的时候应该用

#undef TEXT

否则简单的删除一个宏定义的话会到来许多编译错误。

const

c语言中的常量
int b = 500;
const int* a = &b;(情况1)
int const* a = &b;(情况2)
int* const a = &b;(情况3)
const int* const a = &b;(情况4)

如果const位于*号的左侧,则const就是用来修饰指针指向的变量,即指针指向的是常量。

如果const位于*号的右侧,则const就是用来修饰指针本身,即指针本身是常量。

对于情况1。

int b =500;
const int* a =&b;
*a=600;//错误

对于情况2与情况1相同。

对于情况3。

int b =500,c=600;
int* const a;//错误,没有初始化
int* const a = &b;//正确*
*a=600;//正确
cout<<a++<<endl;//错误

情况4就不需要再说了。

下面我们说下字面常量,字符常量。。。

x = -100.25f;
#define OPEN_SUCCESS 0x00000001
char c= 'a';
char* pChar = "abcdef";//取字符串常量的地址
int* pInt =NULL;

字面常量是程序中出现最多的常量了,例如直接出现的各种进制的数字,字符,字符串等等。由于字面常量只能引用不能修改,所以语言一般都是把它保存在程序的符号表中而不是数据区或是rodata段中。符号表是只读的,实际上市一种访问保护机制,除了字符串外,你无法获取一个字面常量的地址。例如int*p  = &5显然是错误的。当你试图通过常量字符串的地址来修改其中的字符的时候,页会报告“只读”错误。

*(pChar+2)='k';//错误,不能修改字面常量的的内存单元

如果程序中充满了各种各样的字面常量的时候,那么可能存在相同的常量,有些链接器会对这些常量做合并操作。

 

你可以取一个const符号常量的地址:

1)对于基本的数据类型的const常量,编译器会重新再内存中创建一个它的拷贝,你通过其地址访问到的其实是这个拷贝而非原始的符号常量。

2)对于构造类型的const常量,struct,class。实际上它是编译时不允许修改的变量,因此你能够绕过编译器的静态类型的安全检查机制,就可以在运行的时候修改其内容。

const long lng =10;
long* p1 =(long*)&lng;//取常量地址
*p1=100;//"迂回修改"
cout<<*p1<<endl;//1000,修改的是拷贝的内容
cout<<lng<<endl;//10,原始的常量并没有改变。


 

class Integer
{
    public:
       Integer():m_long(10){}
       long m_long;
};
const Integer int_I;
Interger* pInt  = (Integer*)&int_I;//去除常量属性
pInt->m_lng=1000;
cout<<pInt->m_lng<<endl;//1000,习惯const对象
cout<<int_1.m_lng<<endl;//1000,“迂回修改”成功

所以说const也是编译时候检查,防君子不防小人。

还有一点要说的是,在标准C中,const符号常量默认是外连接的,额就是你不能再晾干编译单元中同时定义一个同名的const常量,或是把一个const符号常量放在头文件中而多个编译单元包含这个头文件。所以如果你想在C中把const常量放在头文件中定义,则必须在const 前面加一个static定义,这样每一个包含该头文件的编译单元都会得到一份独立的实体。但是在C++中,const符号默认是内连接的,因此可以定义在头文件中,当不同大的编译单元你都包含该头文件时,编译器认为他们是不同的符号常量,因此某个编译单元独立编译的时候会分别为他们分配存储空间。


const double di;//错误,必须此时进行初始化,后面没有机会了
const double max=10.0;

C++中常量

有的时候wim希望某些常量只是在类中生效。由于#define定义是全局的,不能达到目的,于是想当然的觉得应该用const修饰数据成员来实现。const数据成员的确是存在的,但是其含义却不是我们期望的。非静态的const数据成员是属于每一个对象的成员,只是在该对象的生存期限内是常量,而对于整个类来说它是可变的。除非是static const。

1)不能在类声明中初始化非静态const数据成员。

class A
{
    const int SIZE =100;//错误,不能再类声明中初始化const数据成员,
    int array[SIZE];//错误,未知的SIZE
};

在类的对象被创建之前,编译器无法知道SIZE的值是什么,非静态const数据成员的时候只能在类的构造函数的初始化列表中进行。


class A
{
    A(int size);
    const int SIZE;
};
A::A(int size):SIZE(size)
{
}


那么如何在类中定义恒定的常量呢,有两种办法:

2)类中定义枚举类型常量

class A
{
   enum
  {
      SIZE=100
   };

   int array[SIZE];
};


枚举常量的缺点是不能表示浮点数,这个时候必须用类的static const 常量了。

3)static const常量

class Test{ 
  public: 
    static const int MASK1; 
    static const int MASK2; 
}; 

const int Test::MASK1 = 0xFFFF; 
const int Test::MASK2 = 0xFFFF; 

4)const修饰的成员函数

class Point
{
      int xVal,yVal;
   public:
      int GetY() const;
};

//关键字const必须用同样的方式重复出现在函数实现里,否则编译器会把它看成一个不同的函数;
int Point:GetY() const
{
    return yVal;
}

如果GetY()试图用任何方式改变yVal或调用另一个非const成员函数,编译器将给出错误信息。如果把const放在函数声明前呢,那么表示函数的返回值是常量。

有类如下:

class A
{
     void f()const
    {
    }
};


在c++程序中,类里面的数据成员加上mutable后,修饰为const的成员函数可以修改它了。

class C
{
    public:
       int incr()const
       {
            ++m_count;
       }
    private:
       mutable int m_count;
};

 

const与#define的比较

1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查。

2)const常量可以调试,但是不能对宏常量进行调试。

 sizeof

class A1
{
    public:
      int a;
      static int b;
};

//sizeof(A1)==4,因为静态变量是存放在全局数据区的,而sizeof计算栈中分配的大小,是不会计算在内的。

class A5
{
    public:
      double d;
      float a;
      int b;
      char c;
};

//sizeof(A5)==24;按照8字节对齐


 

struct Z
{
    bool m_ok;
    char m_name[6];
};
//sizeof(Z)==7,按照一字节对齐

struct R
{
   char m_ch;
   double m_width;
   char m_name[6];
};
//sizeof(R)=24,8字节对齐

struct T
{
    int m_no;
    R m_r;
};
//sizeof(T)==32,,24+8,按照8字节对齐


 

class Base
{
     public:
        Base(){}
        ~Base(){}
        virtual void f(int){}
};

class Derived:public Base
{
   public:
      Derived(){}
      ~Derived(){}
      virtual void f(int){}
};

//sizeof(Base)==4,sizeof(Derived)=4

class A
{
};
//sizeof(A)=1

class B:public A
{
};
//sizeof(B)=1
class C:public virtual B
{
};
//sizeof(C)=4,虚继承涉及虚表,所以又虚指针

指针数组  数组指针 函数指针

指针的本质:

我们知道,可执行程序是由指令,数据,地址组成的。当CPU访问内存单元的时候,不论是读取还是写入,首先必须把内存单元的地址加载到地址总线(AB)上,同时将内存电路的“读写控制”设为有效,然后将内存单元中的数据就通过数据总线(DB)流向目标内存单元中。这就是一个内存读写周期。

int* a,b,c //只有a是指针,b和c都是int;类型的变量

数组名字本身就是一个指针,是一个指针常量,即a相当于int* const a,因此a的值不能修改,数组名就是数组第一个元素的内存地址,即a = &a[0]

int a[10]={0};
int b[10]={1,2,3,4};//后面几个数自动初始化为0
a=b;//不仅语义不对,何况a也不能被修改

数组和指针之间存在如下关系。

int a[10] <=> int* const a;
int b[3][4] <=> int (*const b)[4];
int c[3][4][5] <=> int (*const c)[4][5];

数组实际上也是一个可以递归定义的概念:任何维数的数组都可以被看做是由比它少一维的数组组成的一维数组,例如int a[3][4][5]可以看做是由二维数组int b[4][5]组成的一维数组,其长度为3。

 

void output(const int a[],int size)
{
}
void output(const int a[][20],int size)
{
}

函数指针

typedef int (__cdecl *FuncPtr)(const char* );
FuncPtr fp_1 = strlen;
FuncPtr fp_2 = puts;
double (__cdecl* fp_3)(double) = sqrt;

通过函数指针来调用函数有两种方式:

1)直接把函数指针变量当做函数名,然后填入参数

2)将函数指针的反引用作为函数名,然后填入参数

 

int len = fp_1("i am a software engineer");
double d = (*fp_1)(10.25);

 

double (__cdecl* fp[5])(double) ={sqrt,fabs,cos,sin,exp};
for(int k=0;k<5;k++)
{
    cout<<fp[k](10.25)<<endl;
}

 


 

class CTest
{
public:
    void f(void){}
    static void g(void){}
    virtual void h(void){}
};

 

int main()
{
    typedef void (*GFptr)(void);
    GFptr fp = CTest::g;
    fp();
    typedef void(CTest::*MemFuncPtr)(void);
    MemFuncPtr mfp_1 = &CTest::f;
    MemFuncPtr mfp_2 = &CTest::h;
    CTest theObj;
    (theObj.*mfp_1)();
    (theObj.*mfp_2)();

    return 0;
}

指针数组   数组指针

用变量a给出如下定义:

1)一个整型数:int a;

2)一个指向整型数的指针:int* a;

3)一个指向指针的指针,它指向的指针指向一个整形型:int** a;

4)一个有10个整型数的数组:int a[10] => int * const a;

5)一个有10个指针的数组,该指针是指向一个整型数的:int* a[10];(想想int a[10]吧,你就知道这里为什么是int* a[10]了)

6)一个指向有10个整型数数组的指针:(这里可以使int* a吗或是Int* const a;显然不行,你怎么知道是一个指向整型的指针还是一个指向整型数组的指针,指向整型的指针+1就移动sizeof(int),而一个指向整型数组的指针+1就移动sizeof(int)*数组大小)

     所以应该是int (*a)[10]在;这个可以这么解释(*a)说明是一个指针,指针的类型是什么呢,int [10];

7)一个指向函数的指针,该函数有一个整形参数并且返回一个整形:int (*a)(int);

8)一个有10个指针的数组,该指针指向一个函数,该函数原型同上:int (*a[10])(int);

 

我们看下下面程序的输出是什么?

#include<stdio.h>
#include <iostream>
using namespace std;
int main()
{
    int v[2][10]={{1,2,3,4,5,6,7,8,9,10},{11,12,13,14,15,16,17,18,19,20}};
    int (*a)[10]=v;
    cout<<**a<<endl;
    cout<<**(a+1)<<endl;
    cout<<*(*a+1)<<endl;
    cout<<*(a[0]+1)<<endl;
    cout<<*(a[1])<<endl;
    
    return 0;
}

输出结果为:1,11,2,2,11

 

int (*ptr)[] (指向整形数组的指针)

int* ptr[],int *(ptr[]),(指针数组,可以看到和上面就差一个括号,但是差别就很大,括号加在不同的位置起到的效果就完全不同)

int ptr[]

 

#include <iostream>
using namespace std;
int main()
{
    char* a[]={"hello","the","world"};
    char** pa =a;
    pa++;
    cout<<*pa<<endl;//输出the
    return 0;
}

 

char* p1 = new char[5][3];//ERROR
int* p2 = new int[4][6];//ERROR
char (*p3)[4] = new char[5][4];//OK
int (*p4)[5]=new int[3][5];//OK
char (*p5)[5][7]=new char[20][5][7];//OK


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值