C++语言学习(二)——C++对C语言基础语法的扩展

C++语言学习(二)——C++对C语言基础语法的扩展

C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面。

一、实用性增强

C语言中变量的定义必须在作用域开始的位置进行定义。

#include <stdio.h>

int main(int argc, char *argv[])
{
    int i;//定义变量
    int j;

    //使用变量
    for(i = 0; i < 10; i++)
    {
        for(j = 0; j < 10; j++)
        {
        }
    }
    //error: ‘for‘ loop initial declarations are only allowed in C99 mode
    for(int k = 0; k < 10; k++)
    {
    }

    return 0;
}

C++更强调语言的实用性,C++中所有的变量可以在需要使用时再定义。

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int a = 7;
    //使用时定义变量
    for(int i = 0; i < 10; i++)
    {
        for(int j = 0; j < 10; j++)
        {
        }
    }
    return 0;
}

二、类型增强

1、类型检查更严格

在C语言中:

const int a = 100;
int *p = &a;

在C++语言中:

 const int a = 100;//必须在定义的时候初始化
 const int *p = &a;//类型必须严格匹配

在C++语言中不能隐式转换数据类型。
error: invalid conversion from ‘const int*‘ to ‘int*‘ [-fpermissive]

2、bool类型

C语言中,没有定义bool类型,表示真用非0,假用0。
C++语言中,定义了自己的bool类型,真为true,假为false,是基本类型。
sizeof(bool) = 1
C++编译器会将非0值转换为true,0转换为false

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    bool a = 0;
    printf("a = %d\n", a);//0
    bool b = -1;
    printf("b = %d\n", b);//1
    b = b + 1;//1 + 1 = 2 => 1
    printf("b = %d\n", b);//1
    printf("sizeof(bool) = %d\n", sizeof(bool));//1
    return 0;
}

3、枚举

C语言中枚举本质就是整型,枚举变量可以用任意整型赋值。而c++中枚举变量,只能用被枚举出来的元素初始化。
在C语言中,枚举的使用

#include <stdio.h>
enum weekday
{
monday,tuesday,wednesday,thursday,friday,saturday,sunday
};
int main(int argc, char **argv)
{
    enum weekday day = monday;
    enum weekday a = sunday;
    enum weekday b = 100;
//weekday c = sunday;错误用法,需要使用enum声明
    printf("%d %d %d\n", day, a, b);
    return 0;
}

在C++语言中枚举的使用

enum weekday
{
    monday,tuesday,wednesday,thursday,friday,saturday,sunday
};
int main()
{
    weekday day = sunday;
    enum weekday a = monday;
    cout<<day<<" "<<a<<endl;
    return 0;
}

4、表达式做左值

C语言中表达式不可以做左值,C++中某些表达式可以做左值。
(a = b) = 6;

5、register关键字

register关键字请求编译器将局部变量存储到寄存器中。
C语言中,无法获取register关键字修饰的局部变量的地址。
C++语言中,C++依然支持register关键字,但C++编译器对register关键字进行了优化,C++语言中可以获取register变量的地址。对于早期的C++编译器,C++编译器发现程序中需要获取register关键字修饰的变量地址时,register关键字对变量的声明失效;对于现代C++编译器,register关键字的存在只是为了兼容C语言,register关键字本身已经无任何意义。

6、全局变量

C语言中可以重复定义多个重名的全局变量,同名的全局变量被链接到全局数据区的一个地址空间。

#include <stdio.h>

//定义重名的全局变量合法
int a;
int a;

int main(int argc, char *argv[])
{

    return 0;
}

C++语言不允许定义多个同名的全局变量。

#include <iostream>

using namespace std;

//定义重名的全局变量是非法的
int a;
int a;
//error: redefinition of ‘int a‘

int main(int argc, char *argv[])
{

    return 0;
}

7、struct

C语言中,struct定义了一种变量的集合,struct定义的标识符不是一种新类型。C语言中的struct内部不可以定义函数。

#include <stdio.h>

struct tag_student
{
    const char* name;
    int age;
};
typedef struct tag_student Student;

int main(int argc, char *argv[])
{
    //合法定义
    Student s;
    s.name = "lee";
    s.age = 30;
    //非法定义
    tag_student ts;//error: unknown type name ‘tag_student‘
    //合法定义
    struct tag_student sts;
    sts.name = "bauer";
    sts.age = 23;
    return 0;
}

C语言中只有使用typedef关键字重命名struct后才可以使用Student定义变量。
C++语言中struct用于定义一种全新的类型,可以使用struct定义的标识符直接定义变量。

#include <iostream>

using namespace std;

struct Student
{
    const char* name;
    int age;
};

int main(int argc, char *argv[])
{
    Student s;
    s.name = "bauer";
    s.age = 20;
    return 0;
}

8、函数参数

在C语言中,函数在定义时没有给出参数、返回值的类型,默认为int。
int f()表示返回值为int,接受任意参数的函数
f(void)表示返回值为int的无参数函数

#include <stdio.h>

//接受任意个参数,返回int类型
func1()
{
    return 30;
}
//无参函数,返回int类型
func2(void)
{
    return 20;
}
//参数i默认为int类型
void func3(i)
{
    printf("i = %d\n", i);
}

int main(int argc, char *argv[])
{
    int a = func1(1,2,3);
    printf("a = %d\n", a);//30
    int b = func2();
    printf("b = %d\n", b);//20
    func3(10);//10
    return 0;
}

C++语言中,所有的标识符都必须显示的声明类型。C语言中的默认类型在C++中是不合法的。

#include <iostream>

using namespace std;

//error: ISO C++ forbids declaration of ‘func1‘ with no type [-fpermissive]
func1()
{
    return 30;
}
//无参数,返回int
int func2()
{
    return 20;
}

int func3(void)
{
    return 10;
}

int main(int argc, char *argv[])
{
    //error: too many arguments to function ‘int func2()‘
    int a = func2(20);
    int b = func2();
    int c = func3();
    return 0;
}

9、const

C语言中,const修饰的变量是只读的,本质还是变量,可以借助指针修改变量空间的值;const修饰的变量会分配存储空间,const修饰的局部变量分配在栈上,const修饰的全局变量分配在只读存储区,修改const修饰的全局变量的值将会导致异常错误;const只在编译期有效,在运行期无效;const关键词用于向编译器表明const修饰的变量不能做左值。

#include <stdio.h>

//const全局变量,分配在只读存储区
const int number = 10;

int main(int argc, char *argv[])
{
    const int c = 0;
    int* p = (int*)&c;//编译器会为c分配空间
    printf("Begin...\n");
    *p = 5;
    printf("c = %d\n", c);//5
    c = 10;//error: assignment of read-only variable ‘c‘
    int* cp = &number;
    *cp = 100;//程序异常
    const int number = 10;
    int array[number] = {0};//只读变量,编译时无法确定其值
    //error: variable-sized object may not be initialized
    printf("End...\n");
    return 0;
}

C++语言中,对C语言基础的const进行了优化处理。编译器编译过程中遇到const修饰的标识符时,会将const修饰的标识符放入符号表中。如果后续编译过程中发现const修饰的标识符时,直接使用符号表中const修饰的标识符对应的值直接替换。但在以下情况下C++编译器会给const声明的常量分配空间:
A、const修饰的常量为全局(extern修饰),并且需要在其它文件中使用
B、使用&操作符对cosnt常量取地址
C++编译器虽然会对const常量分配空间,但不会使用其存储空间的值。
const常量的判别:
A、只有用字面量初始化的const常量才会进入符号表
B、使用其他变量初始化的const常量仍然是只读变量
C、被volatile修饰的const常量不会进入符号表
const引用类型与初始化变量的类型相同时,初始化变量为只读变量;不同时,生成一个新的只读变量。

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    const int a = 10;
    //error: invalid conversion from ‘const int*‘ to ‘int*‘
    int* p = &a;
    int* cp = (int*)&a;
    *cp = 100;
    printf("a = %d\n", a);//10
    printf("*cp = %d\n", *cp);//100

    int b = 3;
    //使用其它变量初始化的const常量是只读变量
    const int c = b;
    //error: variable-sized object ‘array‘ may not be initialized
    int array[c] = {0};
    //使用volatile修饰的const常量不会进入符号表
    volatile const int d = 10;
    //error: variable-sized object ‘varray‘ may not be initialized
    int varray[d] = {0};

    return 0;
}

C++语言中const与宏定义的不同在于,const常量由编译器处理,编译器会对const常量进行类型检查和作用域检查,而宏定义由预处理器进行处理,是单纯的文本替换。

#include <iostream>

using namespace std;

void func1()
{
    #define NUMBER 100
    const int number = 10;
    printf("NUMBER = %d\n",NUMBER);
    printf("number = %d\n",number);
}

void func2()
{
    //宏定义没有作用域概念,预处理时直接替换
    printf("NUMBER = %d\n",NUMBER);
    printf("number = %d\n",number);
    //‘number‘ was not declared in this scope
}

int main(int argc, char *argv[])
{
    func1();
    func2();
    const int number = 10;
    //编译时使用符号表的值替换
    int array[number] = {0};
    return 0;
}

10、三目运算符

C语言中,三目运算符返回变量的值,三目运算符表达式不能做左值使用。
C++语言中,三目运算符可直接返回变量本身,三目运算符表达式可以作为左值使用。但是当三目运算符表达式可能返回的值中有一个是常量值,则三目运算符表达式不能作为左值使用。
C++中,当三目运算符表达式可能返回的都是变量时,返回的是变量的引用;当三目运算符表达式可能返回的有常量值时,返回的是值。

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int a = 3;
    int b = 2;
    //返回变量本身,可以做左值
    (a>b?a:b) = 10;
    printf("a>b?a:b = %d\n",a>b?a:b);
    //返回变量的值,不能做左值
    (a<b?1:b) = 20;
    //error: lvalue required as left operand of assignment
    return 0;
}

三、输入与输出

1、cin&cout

cin和cout是C++的标准输入流和输出流,在头文件 iostream 中定义。
流名 含义 隐含设备 流名 含义 隐含设备
cin 标准输入 键盘 cerr 标准错误输出 屏幕
cout 标准输出 屏幕 clog cerr 的缓冲输出 屏幕

int main()
{
    char name[30];
    int age;
    cout<<"pls input name and age:"<<endl;
    cin>>name;
    cin>>age;
    cout<<"your name is: "<<name<<endl;
    cout<<"your age is: "<<age<<endl;
    return 0;
}

2、格式化

A、按进制输出数据类型

cout<<dec<<i<<endl;
cout<<hex<<i<<endl;
cout<<oct<<i<<endl;

B、设置域宽,设置左右对齐及填充字符

int main()
{
    cout<<setw(10)<<1234<<endl;
    cout<<setw(10)<<setfill(‘0‘)<<1234<<endl;
    cout<<setw(10)<<setfill(‘0‘)<<setiosflags(ios::left)<<1234<<endl;
    cout<<setw(10)<<setfill(‘-‘)<<setiosflags(ios::right)<<1234<<endl;
    return 0;
}

C、实型数据的设置

cout<<setw(5)<<‘a‘<<endl<<setw(5)<<100<<endl
        <<setprecision(2)<<setiosflags(ios::fixed)<<120.00<<endl;

四、函数重载

1、函数重载简介

C语言中不允许重名函数的存在。
C++语言中为了简化编程允许重名函数的存在,即使用同一个函数名定义不同的函数,重名函数称为函数重载。
重载函数本质是定义的相互独立的不同函数。当函数名和不同的参数搭配时函数的含义不同。

int abs(int a)
{
    return a>0? a:-a;
}
double abs(double a)
{
    return a>0? a:-a;
}

2、函数重载规则

函数重载的规则如下:
A、函数名相同。
B、参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
C、返回值类型不同则不可以构成重载。

#include <iostream>

using namespace std;

int func(int a, int b, int c = 0)
{
    return a + b + c;
}

int func(int a, int b)
{
    return a + b;
}

int main(int argc, char *argv[])
{
    //函数调用时出现二义性
    int x = func(1,2);
    //error: call of overloaded ‘func(int, int)‘ is ambiguous
    return 0;
}

3、函数重载的匹配规则

编译器调用重载函数的匹配规则如下:
A、将所有同名函数作为候选者
B、寻找可行的候选参数
C、匹配成功或失败
函数重载的匹配规则如下:
A、精确匹配实参,找到则调用。
B、通过默认参数能够匹配实参
C、通过默认类型转换匹配实参
通过默认类型转换匹配实参时,通过隐式转换寻求一个匹配,找到则调用。
C++允许int到long和double的隐式类型转换,因此在函数重载时会引起二义性,解决方法是在调用时强转类型。

#include <iostream>

using namespace std;

int func(int a, int b)
{
    return a + b;
}

double func(double a, double b,double c)
{
    cout << "func(double a, double b,double c)"<<endl;
    return a + b + c;
}

long func(long a, long b,long c)
{
    cout << "func(long a, long b,long c)"<<endl;
    return a + b + c;
}

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 2;
    int c = 3;
    //int xa = func(a,b,c);//出现二义性
    int x = func((long)a,(long)b,(long)c);
    printf("x = %d\n", x);

    return 0;
}

编译器调用重载函数匹配失败的规则:
A、如果最终找到的候选函数不唯一,则出现二义性,编译报错。
B、如果无法匹配所有候选者,函数未定义,编译报错。
重载函数使用默认参数可能会造成二义性。

#include <iostream>

using namespace std;

int func(int a, int b, int c = 0)
{
    return a + b + c;
}

int func(int a, int b)
{
    return a + b;
}

int main(int argc, char *argv[])
{
    //函数调用时出现二义性
    int x = func(1,2);
    //error: call of overloaded ‘func(int, int)‘ is ambiguous
    return 0;
}

4、函数重载的底层实现

C++利用name mangling(倾轧)技术,来改名函数名,区分参数不同的同名函数。
C++的name mangling实现使用 v c i f l d表示void char int float long double及其引用。

    void func(char a); // func_c(char a)
    void func(char a, int b, double c);//func_cid(char a, int b, double c);

name mangling发生在两个阶段,.cpp编译阶段和.h的声明阶段。只有两个阶段同时进行,才能匹配调用。

#include <iostream>

using namespace std;
//函数类型:int(int,int)
int func(int a, int b)
{
    return a + b;
}
//函数类型:double(double, double, double)
double func(double a, double b,double c)
{
    cout << "func(double a, double b,double c)"<<endl;
    return a + b + c;
}
//函数类型:long(long,long,long)
long func(long a, long b,long c)
{
    cout << "func(long a, long b,long c)"<<endl;
    return a + b + c;
}

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 2;
    int c = 3;
    //int func(int a, int b)
    printf("x1 = 0x%X\n", (int(*)(int,int))func);
    //long func(long a, long b,long c)
    printf("x2 = 0x%X\n", (long(*)(long,long,long))func);
    //double func(double a, double b,double c)
    printf("x3 = 0x%X\n", (double(*)(double,double,double))func);

    return 0;
}

使用nm工具查看main.o文件中符号表信息的命令如下:
nm.exe -a main.o
func重载函数的符号表信息如下:

00000036 T __Z4funcddd
00000029 T __Z4funcii
00000090 T __Z4funclll

上述的信息表示代码中的三个重载函数。

5、函数重载与函数指针

将重载函数名赋值给函数指针时,根据重载规则选择与函数指针参数列表一致的函数。重载函数的函数类型与函数指针类型必须严格匹配(不能有任何类型的隐式转换),此时函数返回类型将参与函数类型匹配。
函数重载必须发生在同一个作用域,无法通过函数名得到重载函数的入口地址。
重载函数的函数类型不同。

#include <iostream>

using namespace std;
//函数类型:int(int,int)
typedef int(*pFunc1)(int,int);
int func(int a, int b)
{
    cout << "func(int a, int b)"<<endl;
    return a + b;
}
//函数类型:double(double, double, double)
typedef double(*pFunc2)(double,double,double);
double func(double a, double b,double c)
{
    cout << "func(double a, double b,double c)"<<endl;
    return a + b + c;
}
//函数类型:long(long,long,long)
typedef long(*pFunc3)(long,long,long);
long func(long a, long b,long c)
{
    cout << "func(long a, long b,long c)"<<endl;
    return a + b + c;
}

int main(int argc, char *argv[])
{
    int a = 1;
    int b = 2;
    int c = 3;
    pFunc1 func1 = func;
    int x1 = func1(1,2);//int func(int a, int b)
    printf("func1 = %d\n", x1);
    pFunc2 func2 = func;
    int x2 = func2(1,2,3);//double func(double a, double b,double c)
    printf("func2 = %d\n", x2);
    pFunc3 func3 = func;
    int x3 = func3(1,2,3);//long func(long a, long b,long c)
    printf("func3 = %d\n", x3);

    return 0;
}

重载函数的调用可能会存在隐式类型转换,比如int到long、double类型的转换,但是要函数指针调用重载函数时,函数指针的类型必须与重载函数的类型严格匹配。

6、函数重载的注意事项

函数重载的注意事项如下:
A、函数重载必然发生在同一个作用域中。
B、编译器需要使用参数列表或函数类型进行函数的选择。
C、不能直接通过函数名得到重载函数的入口地址。

五、C++与C的相互调用

1、C++与C的兼容

C++完全兼容C语言,因此必须完全兼容C的类库。由于.c文件的类库文件中函数名并没有发生name manling行为,而在包含.c文件所对应的.h文件时,.h 文件要发生name manling行为,因而会在编译链接时候发生错误。
C++为了避免上述错误的发生,重载了关键字extern。只需要要避免name manling的函数前,加extern "C"如有多个,则extern "C"{}。
C语言标准库中实际上对C++语言程序引用时做了特殊处理,在C++语言编译器编译时使用extern "C"将C语言的标准库函数排除了命名倾轧。
为了确保无论在C、C++编译器中C代码以C语言方式编译:

#ifdef __cplusplus
extern "C"{
#endif
//c-style code
#ifdef __cplusplus
}
#endif

C++编译器不能以C语言方式编译重载函数,C++编译器将函数名和参数列表编译为目标名,C语言编译方式只将函数名作为目标名进行编译。

2、C++代码引用C函数

C++调用C语言编码的.dll时,当包含.dll的头文件或声明接口函数时需要加extern “C”。
add.h源码:

#ifndef ADD_H
#define ADD_H

extern int add(int a, int b);

#endif

add.c源码:

#include "add.h"

int add(int a, int b)
{
    return a + b;
}

main.cpp源码:

#include <stdio.h>

extern "C"
{
#include "add.h"
}
int main()
{
    int c = add(10, 100);
    printf("%d\n", c);
    return 0;
}
gcc add.c -o add.o
g++ add.o main.cpp

3、C代码引用C++函数

C代码中引用C++的函数和变量时,C++头文件需要添加extern “C”,但在C代码中不能直接引用声明了extern “C”的C++头文件,C代码中只需要将C++中定义的extern “C”函数声明为extern类型即可。
add.h源码:

#ifndef ADD_H
#define ADD_H

extern "C" int add(int a, int b);

#endif

add.cpp源码:

#include "add.h"

int add(int a, int b)
{
    return a + b;
}

main.c源码:

#include <stdio.h>
//#include "add.h"   错误
extern int add(int a, int b);

int main()
{
    int x = add(1, 2);
    printf("x = %d\n", x);
    return 0;
}

编译:

g++ -c add.cpp -o add.o
 gcc main.c add.o -lstdc++

或是gcc add.cpp main.c -lstdc++
g++会自动进行C++标准库的连接;用gcc连接C++程序也可以,但需要人为指定连接C++标准库(-lstdc++),否则就会出现undefined reference to __gxx_personality_v/0之类的错误。

六、操作符重载

C++提供了运算符重载机制。可以为自定义数据类型重载运算符。实现构造数据类型也可以像基本数据类型一样的运算特性。

struct COMP
{
    float real;
    float image;
};
COMP operator+(COMP one, COMP another)
{
    one.real += another.real;
    one.image += another.image;
    return one;
}
int main()
{
    COMP c1 = {1,2};
    COMP c2 = {3,4};
    COMP sum = operator+(c1,c2); //c1+c2;
    cout<<sum.real<<" "<<sum.image<<endl;
    return 0;
}

实例代码重载了一个全局的操作符+号用于实现将两个自定义结构体类型相加。本质是函数的调用。

七、函数默认参数

1、函数参数默认值

C++语言中,可以在函数声明时为参数提供一个默认值。当函数调用没有提供参数的值时,使用默认值。

2、默认参数的规则

函数默认参数的规则如下:
A、默认参数的顺序,是从右向左,不能跳跃。
B、定义在前,调用在后(此时定义和声明为一体),默认参数在定义处;声明在前,调用在后,默认参数在声明处。
C、一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统无法确认是重载还是默认参数。
函数调用时参数从左到右匹配,如果一个参数使用了默认值,则后续参数必须使用默认值。

3、使用示例

A、单个参数

#include <iostream>
#include <time.h>

using namespace std;

void weatherForcast(char * w="sunny")
{
    time_t t = time(0);
    char tmp[64];
    strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A ",localtime(&t) );
    cout<<tmp<< "today is weahter "<<w<<endl;
}

int main(int argc, char *argv[])
{
    //sunny windy cloudy foggy rainy
    weatherForcast();
    weatherForcast("rainny");
    weatherForcast();
    return 0;
}

B、多个参数

#include <iostream>

using namespace std;

float volume(float length, float weight = 4,float high = 5)
{
    return length*weight*high;
}

int main(int argc, char *argv[])
{
    float v = volume(10);
    float v1 = volume(10,20);
    float v2 = volume(10,20,30);
    cout<<v<<endl;
    cout<<v1<<endl;
    cout<<v2<<endl;
    return 0;
}

4、函数占位参数

C++语言中可以为函数提供占位参数,占位参数只有类型声明,没有参数名声明。由于C++类型检查较为严格,为兼容C语言,可以将函数参数默认值和占位参数结合使用。
C语言中func函数如下:

#include <stdio.h>

void func()
{

}

int main(int argc, char *argv[])
{
    func(5,10);
    return 0;
}

C语言中func函数接收任意个数的参数。
C++语言中对func函数增加占位参数可以使C语言中的func函数快速地满足C++语言的语法要求,代码如下:

#include <iostream>

using namespace std;

void func(int x, int = 0)
{

}

int main(int argc, char *argv[])
{
    func(5,10);
    return 0;
}

八、引用

1、引用简介

变量名,本身是一段内存的引用,即别名(alias)。引用,是为己有变量起一个别名。
Type& name = var;

     int a;
     int &b = a;

普通引用在定义时必须使用同类型的变量进行初始化。

2、引用的规则

A、引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故 而类型与原类型保持一致,且不分配内存,与被引用的变量有相同的地址。
B、声明的时候必须初始化,一经声明,不可变更。
C、可对引用再次引用。多次引用的结果,是某一变量具有多个别名。
D、&符号前有数据类型时,是引用。其它皆为取地址。

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int a = 3;
    int c = 6;
    float f = 3.14;
    int& b = a;
    printf("&a = 0x%X\n", &a);
    printf("&b = 0x%X\n", &b);
    //error: redeclaration of ‘int& b‘
    int& b = c;//error
    //给b赋值
    b = c;
    //引用的类型必须与变量类型相同
    int& d = f;//error
    //error: invalid initialization of reference of type ‘int&‘ from expression of type ‘float‘

    //引用在定义时必须初始化
    int& rd;//error
    //error: ‘rd‘ declared as reference but not initialized
    //引用不可以使用字面值初始化
    int& r = 10;//error
    //error: invalid initialization of non-const reference of type ‘int&‘ from an rvalue of type ‘int‘
    return 0;
}

3、引用的应用

函数中的引用形参不需要进行初始化,函数调用时进行初始化。

#include <iostream>

using namespace std;
//引用
void swap(int &a, int &b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
    printf("swap(int &a, int &b)\n");
}
//指针
void swap(int* a, int* b)
{
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
    printf("swap(int* a, int* b)\n");
}

int main(int argc, char *argv[])
{
    int a = 3;
    int b = 6;
    swap(a,b);
    printf("a = %d\n",a);//6
    printf("b = %d\n",b);//3
    swap(&a,&b);
    printf("a = %d\n",a);//3
    printf("b = %d\n",b);//6
    return 0;
}

4、引用的提高
A、可以定义指针的引用,但不能定义引用的引用。

  int a;
    int* p = &a;
    int*& rp = p; // ok
    int& r = a;
    int&& rr = r; // error

B、可以定义指针的指针(二级指针),但不能定义引用的指针。

  int a;
    int* p = &a;
    int** pp = &p; // ok
    int& r = a;
    int&* pr = &r; // error

C、可以定义指针数组,但不能定义引用数组,可以定义数组引用。

  int a, b, c;
    int* parr[] = {&a, &b, &c}; // ok
    int& rarr[] = {a, b, c}; // error
    int arr[] = {1, 2, 3};
    int (&rarr)[3] = arr; // ok 的

数组是连续的存储空间,数组中的元素如果是引用,会导致数组的元素存储不连续。引用数组会破坏数组存储空间的连续性。

5、const引用

const引用所引用的对象必须是const的,将普通引用绑定到const引用对象是不合法的。
const type& name = var;
const引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化,const引用让变量具有只读属性,是const引用与普通引用最大的区别。
非const引用只能绑定到与该引用同类型的对象。
当const引用使用字面常量值初始化时,C++编译器会为常量值分配空间,使用字面常量对const引用初始化将生成一个只读变量。

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int a = 10;
    const int& ra = a;//const引用,为只读变量
    //只读变量不能作为左值
    ra = 100;//error: assignment of read-only reference ‘c‘
    int* p = (int*)&ra;
    *p = 5;
    printf("ra = %d\n", ra);//5
    const int& rb = 10;//rb为只读变量,占用内存空间
    //只读变量不能作为左值
    rb = 100;//error
    int arraya[ra] = {0};//error
    //error: variable-sized object ‘arraya‘ may not be initialized
    int arrayb[rb] = {0};//error
    //error: variable-sized object ‘arrayb‘ may not be initialized
    double pi = 3.14;
    int& rpi = pi;//非法
    //error: invalid initialization of reference of type ‘int&‘ from expression of type ‘double‘
    const int& crpi = pi;//合法
    printf("rpi= %d\n",crpi);//3
    return 0;
}

6、引用的本质

C++编译器在编译过程中使用指针常量作为引用的内部实现,因此引用所占用空间大小与指针相同。引用的本质是一个指针常量。
type & name&lt;====&gt; type * const name;
使用引用时不能返回局部变量的引用

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    printf("sizeof(char&) = %d\n", sizeof(char&));//1
    char c = ‘a‘;
    char& rc = c;
    printf("sizeof(char&) = %d\n", sizeof(rc));//1
    return 0;
}

上述代码的汇编代码如下:

将字符’a’(0x61)存储到指针寄存器0x1b,将0x1b放入eax数据寄存器中,再将eax数据寄存器存储内容放入指针寄存器0x1c中。

九、new/delete

C语言中提供了malloc和free两个系统函数,完成对堆内存的申请和释放。而C++则提供了两关键字new和delete。

1、new

new分配内存空间时,不能保证按需分配,分配内存空间大小可能会大于所需空间大小。因此,new会分配至少申请大小的内存空间。
A、开辟单变量地址空间

  int *p = new int; //开辟大小至少为sizeof(int)空间
    int *a = new int(5); //开辟大小至少为sizeof(int)空间,并初始化为 5

B、开辟数组空间
一维: int a = new int[100];//开辟一个大小不少于400字节的整型数组空间
二维: int (
a)[6] = new int[5][6]
三维: int (*a)[5][6] = new int[3][5][6]

2、delete

A、释放单变量空间

int *a = new int;
delete a; //释放单个 int 的空间

B、释放数组空间

int *a = new int[5];
delete []a; //释放 int 数组空间

3、new/delete应用

  int *p = new int(5);
    cout<<*p<<endl;
    delete p;

    char *pp = new char[10];
    strcpy(pp,"china");
    cout<<pp<<endl;
    delete []pp;

  string *ps = new string("china");
    cout<<*ps<<endl; //cout<<ps<<endl;
    delete ps;

  char **pa= new char*[5];
    memset(pa,0,sizeof(char*[5]));
    pa[0] = "china";
    pa[1] = "america";
    char **pt = pa;
    while(*pt)
    {
        cout<<*pt++<<endl;
    }
    delete []pt;

  int (*qq)[3][4] = new int[2][3][4];
    delete []qq;

4、返回值

//C++ 内存申请失败会抛出异常
try{
    int *p = new int[10];
}
catch(const std::bad_alloc e)
{
    return -1;
}
//C++ 内存申请失败不抛出异常版本
int *q = new (std::nothrow)int[10];
if(q == NULL)
    return -1;

5、注意事项

C++中堆空间的分配和释放注意事项如下:
A、new/delete 是关键字,效率高于 malloc 和 free.
B、配对使用,避免内存泄漏和多重释放。
C、避免交叉使用。比如 malloc 申请的空间去 delete,new 出的空间被 free;
D、重点用在类对像的申请与释放。申请的时候会调用构造器完成初始化,
释放的时候,会调用析构器完成内存的清理。

6、malloc与new区别

malloc与new的区别如下:
A、new是C++关键字,malloc是C语言库函数
B、new以具体类型为单位进行内存分配,malloc以字节位单位分配内存
C、new在申请单个类型变量时可以进行初始化,malloc不具备
D、new在所有C++编译器中都支持,malloc在某些系统开发中不可调用
E、new能够触发构造函数的调用,malloc仅分配需要的内存空间
F、对象的创建只能使用new,malloc不适合面向对象开发

7、free与delete的区别

free与delete的区别如下:
A、delete是C++关键字,free是库函数
B、delete在所有C++编译器中都支持,free在某些系统开发中不可调用
C、delete能够触发析构函数的调用,free仅归还分配的内存空间
D、对象的销毁只能使用delete,free不适合面向对象开发
E、free可以归还new申请的内存空间,但不会调用析构函数,可能会造成内存泄漏
F、delete可以释放malloc分配的内存空间,但会调用析构函数,会造成其他问题。

十、内联函数

1、内联函数简介

C语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但宏函数的处理发生在预处理阶段,缺少作用域检查和类型检查。
C++提供了inline关键字,请求C++编译器将一个函数进行内联编译(C++编译器可以拒绝),C++编译器会直接将内联的函数体代码插入函数调用的地方。
内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。

inline int sqr(int x)
{
    return x*x;
}

2、内联函数的优缺点

内联函数的优点:避免调用时的额外开销(入栈与出栈操作)
内联函数的缺点:内联函数的函数体在代码段中会出现多个“副本”,因此会增加代码段的空间。
内联函数的本质:以牺牲代码段空间为代价,提高程序的运行时间的效率。
内联函数的适用场景:函数体很“小”,且被“频繁”调用。

3、内联函数示例

Inline关键字是对编译器的建议,如果编译器认为inline声明的函数可以内联,则编译器会将函数内联,如果编译器认为inline声明的函数的函数体太长,则不会内联,按普通函数处理。

using namespace std;

inline int func(int a, int b)
{
    return a*a + b*b;
}

int main(int argc, char *argv[])
{
    int a = 3;
    int b = 4;
    int c = func(a,b);
    return 0;
}

上述代码在QtCreator+MinGW编译器下调试时,查看汇编代码如下:

调用func函数时,C++编译器没有将func函数内联,仍然使用函数调用。
inline声明的函数,内联请求可能被C++编译器拒绝。
现代C++编译器能够进行编译优化,一些函数即使没有inline关键字声明也能够内联编译,同时现代C++编译器提供了扩展的语法,能够对函数进行强制内联。如现代G++编译器使用__attribute__((always_inline))声明强制内联,MSVC编译器使用__forceinline声明强制内联,不再使用inline关键字。

#include <iostream>

using namespace std;

__attribute__((always_inline))
int func(int a, int b);

int main(int argc, char *argv[])
{
    int a = 3;
    int b = 4;
    int c = func(a,b);
    return 0;
}

int func(int a, int b)
{
    return a*a + b*b;
}

上述代码在QtCreator+MinGW编译器中进行调试时,main函数中func函数调用代码的汇编代码如下:

MinGW编译器已经对func函数进行了内联。

4、内联函数使用的限制

C++中使用inline关键字内联编译函数的限制:
A、不能存在任何形式的循环语句
B、不能存在过多的条件判断语句
C、函数体不能过于庞大
D、不能对函数进行取地址操作
E、函数内联声明必须在调用语句前
对于现代C++编译器的扩展语法提供的强制内联不受上述条件限制。

十一、类型强制转换

1、C++类型转换

C语言中的类型转换是强制转换,任何类型间都可以转换,过于粗暴。
C++语言引入了static_cast、dynamic_cast、const_cast、reinterpret_cast四个关键字处理不同类型间的转换。

2、静态类型转换

静态类型转换是在编译期内即可决定其类型的转换。
静态类型转换的使用场合:
A、用于基本类型间的转换
B、不能用于基本类型指针间的转换
C、用于有继承关系类对象间的转换和类指针间的转换(转换一般从子对象向父对象转换)
语法格式:

static_cast<目标类型> (标识符)

应用实例:

#include <stdio.h>

class A
{
private:
    int a;
    int b;
public:
    A()
    {
        a = 0;
        b = 0;
    }
    void print()
    {
        printf("a = %d, b = %d\n", a, b);
    }

};

class B : public  A
{
private:
    int c;
    int d;
public:
    void display()
    {
        printf("c = %d, d = %d\n", c, d);
    }

};

class C
{
public:
    void print()
    {
        printf("hello\n");
    }
};

int main()
{
    float f = static_cast<float>(9)/10;//基本类型的转换
    printf("f = %f\n", f);
    A a;
    B b;
    A aa = static_cast<A>(b);//将子类对象转换为父类对象
    aa.print();
    //B bb = static_cast<B>(a);//不能将父对象转换为子对象
    A* pa = static_cast<A*>(&a);//在同类型对象指针间转换
    pa->print();
    pa = static_cast<A*>(&b);//将子类对象指针转换为父类对象指针
    pa->print();
    B* pb = static_cast<B*>(&b);
    pb->display();
    pb = static_cast<B*>(&a);
    pb->display();
    //C c = static_cast<C>(a);//没有转换构函数,不能将A类型转换为C类型
    //c.print();
    return 0;
}

3、动态类型转换

动态类型转换的使用场合:
A、用于有继承关系的类指针间的转换
B、有交叉关系的类指针间的转换
C、具有类型检查
D、必须有虚函数支持
语法格式:

dynamic_cast<目标类型> (标识符)

用于有直接或间接继承关系的指针(引用)的强制转换
转换指针成功将会得到目标类型的指针,转换失败将得到一个空指针;
转换引用成功将得到目标类型的引用,转换失败将得到一个异常操作信息。
使用实例:

#include <iostream>
#include <string>

using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    virtual ~Base()
    {
        cout << "Base::~Base()" << endl;
    }
};

class Derived : public Base
{
};

int main()
{
    Base* p = new Derived;
    Derived* pd = dynamic_cast<Derived*>(p);//将指向子类对象的父类指针转换为子类指针,转换成功
    if( pd != NULL )
    {
        cout << "pd = " << pd << endl;
    }
    else
    {
        cout << "Cast error!" << endl;
    }
    delete p;
    cout << endl;
    p = new Base;
    pd = dynamic_cast<Derived*>(p);//将指向父类对象的父类指针转换为子类指针,转换失败
    if( pd != NULL )
    {
        cout << "pd = " << pd << endl;
    }
    else
    {
        cout << "Cast error!" << endl;
    }
    delete p;
    return 0;
}

4、常量类型转换

常量类型转换的使用场合:
A、用于去除变量的只读属性
B、目标类类型只能是指针或引用
语法格式:

const_cast<目标类型> (标识符) //目标类类型只能是指针或引用。

const_cast将转换掉表达式的const属性
应用实例:

#include <iostream>

using namespace std;

int main()
{
    const int& a = 10;
    int& b = const_cast<int&>(a);//将a的只读属性去除,并初始化b
    b = 0;
    cout << "a = " << a << endl;//0
    cout << "b = " << b << endl;//0
    const int c = 100;
    int& d = const_cast<int&>(c);//为c分配一个只读空间
    //int x = const_cast<int>(c);//error,目标类型只能为指针和引用
    d = 1000;
    cout << "c = " << c << endl;//100
    cout << "d = " << d << endl;//1000

    return 0;
}

5、重解释类型转换

重解释类型转换使用场合:
A、用于指针类型间的强制转换
B、用于整数和指针类型间的强制转换
语法格式:

reinterpret_cast<目标类型> (标识符)

为数据的二进制形式重新解释,但是不改变其值。
使用实例:

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int a = 100;
    char c = ‘a‘;
    int* pa = reinterpret_cast<int*>(&c);
    printf("*pa = %d\n", *pa);//687781729
    printf("*pa = %c\n", *pa);//‘a‘
    //int x = reinterpret_cast<int>(c);//error
    //int y = reinterpret_cast<int>(1.1);//error

    return 0;
}

十二、命名空间

1、命令空间简介

C语言中,只有一个全局作用域,所有的全局标识符共享一个作用域,因此标识符之间可能存在冲突。
C++语言中,提出了命名空间的概念。命名空间将全局作用域分为不同的部分,不同命令空间中的标识符可以重名而不会发生冲突,命名空间可以嵌套。全局作用域即默认命名空间。

2、默认命名空间(global &&function)

global scope是一个程序中最大的scope,是引起命名冲突的根源。C语言没有从语言层面提供命名空间机制来解决。global scope是无名的命名空间。

3、命名空间的划分

NameSpace是对全局区域的再次划分。
命名空间的声明如下:

namespace NAMESPACE
{
    全局变量 int a;
    数据类型 struct Stu{};
    函数 void func();
}

4、命名空间的使用方法

直接指定命名空间: NameSpace::a = 5;
使用using+命名空间+空间元素:using NameSpace::a; a = 2000;
使用using +namespace+命名空间;

5、命名空间使用示例

#include <iostream>
using namespace std;
namespace MySpace
{
    int x = 1;
    int y = 2;
}
namespace Other {
    int x = 3;
    int y = 4;
}
int main()
{
    {
        using namespace MySpace;
        cout<<x<<y<<endl;
    }
{
        using namespace Other;
        cout<<x<<y<<endl;
    }
{
        MySpace::x = 100;
        Other::y = 200;
        cout<<MySpace::x<<Other::y<<endl;
    }
    return 0;
}

可以使用块语句将命名空间限定在块语句内部。

6、命名空间嵌套

namespace MySpace
{
    int x = 1;
    int y = 2;
    namespace Other {
        int m = 3;
        int n = 4;
    }
}

7、使用命名空间进行协作开发

在实际项目开发中,可以将一个类或者具有相同属性的多个类声明在一个命名空间内,在使用时只需要声明命名空间即可。

#ifndef A_H
#define A_H
namespace XX {
    class A
    {
        public:
            A();
            ~A();
    };
}
#endif // A_H
#include "a.h"
using namespace XXX
{
        A::A()
        {}
        A::~A()
        {}
}

十三、系统string类

除了使用字符数组来处理字符串以外,C++引入了字符串类型。可以定义字符串变量。

1、定义和初始化

  string str;
  str = "china";
    string str2 = " is great ";
    string str3 = str2;

2、类型大小

string str = "china";
cout << sizeof(str) << " " << str.max_size() << " " << str.size()<<endl;

4 1073741820 5

3、运算

A、赋值
string str3 = str2;
B、加法
string combine = str + str2;
C、关系

string s1 = "abcdeg";
string s2 = "12345";
if(s1>s2)
cout<<"s1>s2"<<endl;
else
cout<<"s1<s2"<<endl;

4、string类型数组

string数组是高效的,如果用二维数组来存入字符串数组的话,则容易浪费空间,此时列数是由最长的字符串决定。如果用二级指针申请堆空间,依据大小申请相应的空间,虽然解决了内存浪费的问题,但是操作麻烦。用 string 数组存储,字符串数组的话,效率即高又灵活。

string sArray[10] = {
    "0",
    "1",
    "22",
    "333",
    "4444",
    "55555",
    "666666",
    "7777777",
    "88888888",
    "999999999",
};
for(int i=0; i<10; i++)
{
    cout<<sArray[i]<<endl;
}

5、string类的成员函数

int capacity()const;??//返回当前容量(即string中不必增加内存即可存放的元素个数)
int max_size()const;??? //返回string对象中可存放的最大字符串的长度
int size()const;??????? //返回当前字符串的大小
int length()const;?????? //返回当前字符串的长度
bool empty()const;??????? //当前字符串是否为空
void resize(int len,char c);//把字符串当前大小置为len,并用字符c填充不足的部分

原文地址:http://blog.51cto.com/9291927/2138686

时间: 2024-10-13 04:10:22

C++语言学习(二)——C++对C语言基础语法的扩展的相关文章

嵌入式linux C++语言(二)——C++对C语言基础语法的扩展

嵌入式linux C++语言(二)--C++对C语言基础语法的扩展 C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面. 一.类型增强 1.类型检查更严格 在C语言中: const int a = 100; int *p = &a; 在C++语言中: const int a = 100;//必须在定义的时候初始化 const int *p = &a; 在C++语言中不能隐式转换数据类型. error: invalid conversion

C语言学习书籍推荐《C语言入门经典(第4版)》

霍顿 (Ivor Horton) (作者), 杨浩 (译者) <C语言入门经典(第4版)>的目标是使你在C语言程序设计方面由一位初学者成为一位称职的程序员.读者基本不需要具备任何编程知识,即可通过<C语言入门经典(第4版)>从头开始编写自己的C程序.研读<C语言入门经典(第4版)>,你就可以成为一位称职的C语言程序员.从许多方面来说,C语言都是学习程序设计的理想起步语言.C语言很简洁,因此无须学习大量的语法,就能够开始编写真正的应用程序.除了简明易学外,它还是一种功能非

C语言学习-01第一个C语言程序

一 C语言的历史 C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. 尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准规格写出的C语言程序可在许多电脑平台上进行编译,甚至包含一些嵌入式处理器(单片机或称MCU)以及超级电脑等作业平台. 二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言订定了一套完整的国际标准语法,称为A

JAVA学习(三):Java基础语法(变量、常量、数据类型、运算符与数据类型转换)

Java基础语法(变量.常量.数据类型.运算符与数据类型转换) 1.变量 Java中,用户可以通过指定数据类型和标识符来声明变量,其基本语法为: DataType identifier; 或 DataType identifier = value; 其中,DataType是变量类型,如int/string/char/double/boolean等:identifier是变量名称,即标识符:value就是声明变量的值. 注: a.标识符由数字0-9.大小写字母.下划线.美元符号.人民币符号以及所有

C语言学习(二)--数据类型

基本数据类型 C语言的基本数据类型是:整型.字符型.单精度浮点型.双精度浮点型. 类别 类型名 数据长度 字符 char 8位 短整型 short int.unsigned short int 16位 整型 int.unsigned int 32位 长整型 long int.unsigned long int 32位 单精度浮点型 float 32位 双精度浮点型 double 64位   枚举类型 枚举类型就是指它的值为符号常量而不是字面值的类型.声明形式: enum Door_Type {O

R语言学习(二)

1.复数的向量:用complex()函数生成复数向量 EX: > x<-seq(-pi,pi,by=pi/10)%x的值 > y<-sin(x)%y值> z<-complex(re=x,im=y)%re为实部,im为复部 > plot(z);lines(z)%绘制图形 运行如下: 2.向量的下标运算 R软件提供了十分灵活的访问向量元素和向量子集的功能,x向量的某一个元素可以用x[i]格式访问 EX: > x<-c(1,4,7)> x[2][1]

c语言学习二

编写一个程序,提示用户从键盘输一个星期的薪水(以美元为单位)和工作时数,它们均为浮点数,然后计算并输出每个小时的平均薪水,输出格式如下所示: Your average hourly pay rate is 7 dollars and 54 cents. #include <stdio.h> int main(int argc, char** argv) { float salary = 0.0f; float time = 0.0f; float avg = 0.0f; int a = 0;

Dart语言学习(二) Dart的常量和变量

1.使用var声明变量,可赋予不同类型的值 2.未初始化时候,默认值未null var a; print(a); a = 10; print(a); a = 'Hello Dart'; print(a); 输出: null 10 Hello Dart 3. 使用final声明一个只能赋值一次的变量 4. 使用const声明常量 使用const声明的必须是编译器常量 5.声明变量的方式 var : [编译期]确定[变量类型] dynamic : [运行期]确定[变量类型] bool int dou

脚本语言学习(二)

循环语句与range函数 循环语句 格式: for 变量 in range (参数) 被循环执行的语句 说明:range的参数就是循环的次数.缩进的语句会被循环执行.变量表示每次循环的计数,如果range后面的次数是固定的,那么变量就是0到(次数减1). 例子: 注意:print函数有一种输出方法,是将你要输出的各种信息之间用逗号分隔,那么输出之后,每输出的字符串之间会增加空格,所以hello:和i之间会有一个空格. range函数 作用:产生循环计数序列 使用方法: range(N) 作用是产