05 NDK-C++基础(一)

作者: 凤邪摩羯 | 来源:发表于2022-04-12 09:25 被阅读0次

1 C++对C的加强

1.1 namespace命名空间

1.1.1 C++命名空间基本概念

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字就是针对这种问题而出现的。

1.1.2 C++命名空间定义,使用语法,意义

namespace + 命名空间名称 + { 命名空间成员 }

1.1.3 C++命名空间特点

1. 普通的命名空间

namespace N1 // N1为命名空间的名称
{
    // 命名空间中的内容,既可以定义变量,也可以定义函数
    int a;
    int Add(int left, int right)
    {
        return left + right;
    }
}

2.命名空间可以嵌套

namespace N2
{
    int a;
    int b;
    int Add(int left, int right)
    {
        return left + right;
    }
    namespace N3
    {
        int c;
        int d;
        int Sub(int left, int right)
        {
            return left - right;
        }
    }
}

3. 同一个工程中允许存在多个相同名称的命名空间(编译器会合成到同一个命名空间)

// 编译器最后会合成同一个命名空间中
namespace N1
{
    int Mul(int left, int right)
    {
        return left * right;
    }
}

一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

1.1.4 命名空间的三种使用方式

  1. 加命名空间名称及作用域限定符
int main()
{
    printf("%d\n", N::a);
    return 0;
}
  1. 使用using将命名空间中成员引入
using N::b;
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    return 0;
}
  1. 使用using namespace 命名空间名称引入
using namespce N;
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    Add(10, 20);
    return 0;
}

1.2 新增Bool类型关键字

C++在C语 的基本类型系统之上增加了bool

  1. C++中的bool可取的值只有true和false ,理论上bool只占 1个字节,如果多个bool变量定义在起,可能会各占1个bit,这取决于编译器的实现 ;
  2. true代表真值,编译器内部 1来表 false代表 真值,编译器内部 0来表示;
  3. bool类型只有true( 0)和false(0)两个值;C++编译器会在赋值时将 1值转换为true,0值转换为false
int main04() {
    //c++中的bool类型 要么是true ,要么是false
    //bool 理论上 只占用一个字节 = 8 bit
    //如果多个bool变量定义在一起的时候,可能各自占一个bit
    //取决于编译器的实现
    //true代表真值,编译器内部用1表示, false代表假值,编译器内部用0表示

    cout << "C++中的bool类型" << endl;
    bool b1 = true;//c++编译器
    cout << "sizeof(bool): " << sizeof(bool) << endl;

    //c++编译器会在赋值的时候将非0转换为true,
    b1 = 10;//要么是1,要么是0
    cout << "b1: " << b1 << endl;
    //c++编译器会在赋值的时候将非0转换为true,
    b1 = -1;
    cout << "b1: " << b1 << endl;
    //c++编译器会在赋值的时候将0转化给false
    b1 = 0;
    cout << "b1: " << b1 << endl;

    return 0;
}
C++中的bool类型
sizeof(bool): 1
b1: 1
b1: 1
b1: 0

1.3 C++中的const增强

1.3.1 const基础知识

  • 用法
int main05() {

    cout << "const的基本用法" << endl;
    const int a = 10;
    int const b = 20;//一样的

    //常量指针
    const int *c;//常量指针 const修饰的是指针所指向的变量
    //代表指针所指向的内存空间,不能被修改
    c = &a;
    c = &b;
//    *c = 30;//常量指针不允许修改指针所执向的内存空间

    int a1 = 1;
    //指针常量;
    int *const d = &a1;//指针常量,const修饰的是指针本身,指向不可变

    //常量指针常量;
    const int *const e = &a1;//常量指针常量; 指针的指向不能改变,所指向的内存空间也不能改变

    return 0;
}
  • 含义
    表示初始化后不能被修改的常量,可以修饰基础类型,指针等数据类型

  • 好处

    1. 指针函数参数,可以有效的提高代码的可读性,减少bug
    2. 清楚的区分参数是输入还是输出特性
struct Teacher {
    char name[20];
    int age;
};

/**
 * 参数用于输入特性
 * @param pT
 * @return
 */
int operatorT1(const Teacher *pT) {//常量指针,表示是一种输入参数,保护原来的参数内容不被修改
//    pT->age = 23;
    cout << pT->age << endl;
    pT = NULL;
}

/**
 * 参数用于输出特性
 * @param pT 
 * @return 
 */
int operatorT2(Teacher *const pT) {//指针常量,指向地址不能修改,内容可以修改
    pT->age = 2;//输出参数
//    pT = NULL;
}

1.3.2 C/C++中const的区别

const关键字在C语言编译器器中并不能真正起到锁定变量值的作用,因为我们只要使用一个变量同类型的指针变量p,用p即可直接修改变量的值;
但是在C++编译器中真正做到了锁定变量的值,同样的代码,使用p来修改变量值,然后打印结果,发现变量的值不变。

1.3.3 const和#define相同之处

  • #define 是在编译预处理阶段,只是简单的文本替换
  • const 是由编译器处理的,提供类型检查和作用域检查

1.3.4 const和#define的区别

1.4 三目运算符的增强


#include <iostream>

using namespace std;

int main(void){
  int a=3,b=5;
  (a<b?a:b)=4;
  cout<<a<<endl;
  cout<<b<<endl;
  return 0;

}

1.5 枚举

enum name {
  a,
  b,
  c
}

3 引用

3.1 引用概念

  1. 引用是C++的概念
  2. 引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
  3. C++ 引用和指针三个主要的不同:
    (1) 不存在空引用。引用必须连接到一块合法的内存。
    (2) 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
    (3) 引用必须在创建时被初始化。指针可以在任何时间被初始化。

3.2 引用的基本用法

  • 示例
/**
 * 引用的基本用法
 * @return
 */
int main01() {
    cout << "引用的基本用法" << endl;

//    Teacher b;
    int a = 10;
    // type & name = var;
    int &b = a;//b就是一个引用,请不要用C的语法是思考; 含义:就是给a变量取了一个别名叫做b
    printf("b:%d\n", b);//使用引用后,b就是a
    printf("a:%d\n", a);

    b = 100;
    printf("b:%d\n", b);//修改b就是相当于改a
    printf("a:%d\n", a);

    // type * const 引用相当于指针常量
//    int &c;//这样定义会报错的,普通的引用必须要依附某个变量,必须初始化


    return 0;
}
  • 输出结果
引用的基本用法
b:10
a:10
b:100
a:100

3.3 基本类型的引用

  • 示例
int myswap1(int a, int b) {//值传递,作用域函数内部,不影响外部的x和y
    int tmp = a;
    a = b;
    b = tmp;
}//完成不了我们的功能

/**
 * 使用指针来交换
 * @param a
 * @param b
 * @return
 */
int myswap2(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
/**
 * 使用引用的方式来交换
 * @param a
 * @param b
 * @return
 */
int myswap3(int &a, int &b) {
    int tmp = a;
    a = b;
    b = tmp;
}

/**
 * 基本类型的引用
 * @return
 */
int main02() {
    cout << "基本类型的引用" << endl;

    int x, y;
    x = 10;
    y = 20;
    myswap1(x, y);
    printf("myswap1->x:%d, y:%d \n", x, y);

    myswap2(&x, &y);
    printf("myswap2->x:%d, y:%d \n", x, y);

    //通过myswap3()调用后,a就是x的别名,b就是x的别名,通过操作别,可改变原值
    myswap3(x, y);
    printf("myswap3->x:%d, y:%d \n", x, y);


    return 0;
}
  • 输出
基本类型的引用
myswap1->x:10, y:20 
myswap2->x:20, y:10 
myswap3->x:10, y:20 

3.4 复杂类型的引用

  • 示例
/**
 * 通过指针修改结构体参数
 * @param pT 
 */
void printT1(const Teacher *pT) {
    cout <<"printT1:"<< pT->age << endl;
}

/**
 * 通过别名修改结构体参数
 * @param pT 
 */
void printT2(Teacher &pT) {
    cout <<"printT2 start:"<< pT.age << endl;
    //pT是一个别名
    pT.age = 36;
    cout <<"printT2 end:"<< pT.age << endl;

}

/**
 * 
 * @param pT 
 */
void printT3(Teacher pT) {//值传递
    cout <<"printT3 start:"<< pT.age << endl;
    //pT是一个别名
    pT.age = 37;
    cout <<"printT3 end:"<< pT.age << endl;

}

/**
 * 复杂类型引用的使用
 * @return
 */
int main03() {
    cout << "复杂类型引用的使用" << endl;
    Teacher t1;
    t1.age = 35;//结构体数据赋值

    printT1(&t1);
    printT2(t1);//pT 是t1的别名
    printT3(t1);//pT是形参, t1 copy一份数据给pT //pT = t1;
    printf("t1.age:%d\n", t1.age);

    return 0;
}
  • 输出
复杂类型引用的使用
printT1:35
printT2 start:35
printT2 end:36
printT3 start:36
printT3 end:37
t1.age:36

3.5 引用的本质

  1. 引用就是取别名, 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间,引用在C++的内部实现 就是一个常量指针.
  2. 引用和指针的异同就是:常量指针和指针的异同
  • 示例
struct Student {
    char name[64];
    int age;// 8
    int &a;//8
    int &b;//8
};

/**
 * 通过引用修改(传参的时候不需要取a地址)
 * @param a
 */
void modifyA1(int &a) {
    //引用
    a = 11;
}
/**
 * 通过指针常量修改
 * @param a
 */
void modifyA2(int *const a) {//指针常量,用于输出参数,地址不可变,地址内容可变
    *a = 12;
}
/**
 * 通过指针修改
 * @param p
 */
void modifyA3(int *p) {
    *p = 200;
}

/**
 * 引用的本质
 */
void main04() {
    cout << "引用的本质思考?" << endl;

    int a = 10;
    int &b = a;// 定义一个引用b, b像一个常量

    modifyA1(a);//函数调用的时候,我们程序员不需要取a的地址
    //请思考:对同一内存空间 可以取好几个名字吗?
    //引用就是取别名
    //从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间
    printf("modifyA1->a:%d,b:%d,&a:%p, &b:%p \n",a,b, &a, &b);//这里输出a和b地址一致,说明a和b指向同一个地址

    modifyA2(&a);//指针,我们需手动取地址 , 引用在C++的内部实现 就是一个常量指针
    printf("modifyA2->a:%d,b:%d,&a:%p, &b:%p \n",a,b, &a, &b);//这里输出a和b地址一致,说明a和b指向同一个地址

    modifyA3(&a);//指针,我们需手动取地址
    printf("modifyA3->a:%d,b:%d,&a:%p, &b:%p \n",a,b, &a, &b);//这里输出a和b地址一致,说明a和b指向同一个地址

    printf("sizeof(Student): %d \n", sizeof(Student));
}
  • 输出
引用的本质思考?
modifyA1->a:11,b:11,&a:000000000062fde4, &b:000000000062fde4 
modifyA2->a:12,b:12,&a:000000000062fde4, &b:000000000062fde4 
modifyA3->a:200,b:200,&a:000000000062fde4, &b:000000000062fde4 
sizeof(Student): 88 

3.6 引用做函数参数

引用作为函数参数
C++之所以增加引用类型, 主要是把它作为函数参数,以扩充函数传递数据的功能。
C++ 函数传参:

(1)将变量名作为实参和形参。这时传给形参的是变量的值,传递是单向的。如果在执行函数期间形参的值发生变化,并不传回给实参。因为在调用函数时,形参和实参不是同一个存储单元。// 同 c
(2) 传递变量的指针。形参是指针变量,实参是一个变量的地址,调用函数时,形参(指针变量)指向实参变量单元。这种通过形参指针可以改变实参的值。// 同 c
(3) C++提供了 传递变量的引用。形参是引用变量,和实参是一个变量,调用函数时,形参(引用变量)指向实参变量单元。这种通过形参引用可以改变实参的值。

示例如下:

#include <iostream>
using namespace std;

// 函数声明
void swap(int& x, int& y);

int main()
{
    // 局部变量声明
    int a = 100;
    int b = 200;

    cout << "交换前,a 的值:" << a << endl;
    cout << "交换前,b 的值:" << b << endl;

    /* 调用函数来交换值 */
    swap(a, b);

    cout << "交换后,a 的值:" << a << endl;
    cout << "交换后,b 的值:" << b << endl;

    system("pause");
    return 0;
}

// 函数定义
void swap(int& x, int& y)
{
    int temp;
    temp = x; /* 保存地址 x 的值 */
    x = y;    /* 把 y 赋值给 x */
    y = temp; /* 把 x 赋值给 y  */

    return;
}

结果输出:

交换前,a 的值:100
交换前,b 的值:200
交换后,a 的值:200
交换后,b 的值:100

3.7 引用做函数返回值

  • 内存四区分配
    当引用作为函数的返回值,返回的是一个局部引用(在栈区)时会报错(调用完成后会被回收),不能直接返回


    image.png
  • 返回栈中的引用和指针的错误示例


int getA1() {
    int a;
    a = 10;
    return a;
}

int &getA2() {
    int a;
    a = 20;
    return a;
    //warning: reference to local variable 'a' returned [-Wreturn-local-addr]
    //当引用作为函数的返回值,返回的是一个局部引用时会报错,不能直接返回
}

int *getA3() {
    int a;
    a = 30;
    return &a;
    //warning: address of local variable 'a' returned [-Wreturn-local-addr]
    //当指针引用作为函数的返回值时候,返回的是一个局部变量的地址时候,不能直接返回,
}


int main05() {


    int a1 = getA1();
    cout << "a1: " << a1 << endl;
    
    //当我们把栈上的引用或者指针返回出来的时候,是有问题的,因为栈的变量和方法调用完之后会被系统自动回收
    int a2 = getA2();
//    cout<< "a2: " << a2 <<endl;

    int *a3 = getA3();
//    cout << "a3: " << a3 << endl;

    return 0;
}
  • 引用作为返回值,必须遵守以下规则:
  1. 不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
  2. 不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
  3. 可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
  • 返回静态区中的引用示例
/**
 *
 * @return 返回的是数值
 */
int getG1() {
    //静态变量在静态区
    static int a = 10;
    a++;
    return a;
}

/**
 *
 * @return 返回的是引用
 */
int &getG2() {
    static int b = 20;
    b++;
    cout << "b: " << b << endl;
    printf("&b:%p\n", &b);
    return b;
}

// 指针 引用  const
int main06() {
    cout << "返回static的变量" << endl;

    int g1 = getG1();
    cout << "g1: " << g1 << endl;
    cout << "============================="<< endl;
//    getG1() = 100;//不能把函数返回的数值作为左值

    int g2 = getG2();//引用可以当右值
    cout << "g2: " << g2 << endl;
    printf("&g2:%p\n", &g2);
    cout << "============================="<< endl;

    getG2() = 200;//引用也可以作为左值
    cout << "============================="<< endl;

    getG2();

    return 0;
}
返回static的变量
g1: 11
=============================
b: 21
&b:0000000000409014
g2: 21
&g2:000000000062fde8
=============================
b: 22
&b:0000000000409014
=============================
b: 201
&b:0000000000409014

Process finished with exit code 0

3.8 指针引用

3.9 常引用

4 函数

4.1 inline内联函数

  • 定义
  1. 内联函数 声明时候必须实现,没办法分开,如果分开了C++编译器会取消内联
  2. 内联函数必须放在调用它的方法的前面
  3. C++编译器不一定会允许函数内联
  • 限制:
  1. 不能存在任何形式的循环语句
  2. 不能存在过多的条件判断语句
  3. 函数体不能过于庞大
  4. 不能对函数进行取地址操作
  5. 函数内联声明必须在调用语句之前
#include <iostream>

using namespace std;

//定义一个方法
void printA();//方法声明

//定义一个宏
#define MYFUNC(a, b) ((a) < (b)) ? (a) : (b)

/**
 * C++ 内联函数 inline
 */
inline void printB() {
    //1. 内联函数声明的时候必须实现,没办法分开,如果分开了C++编译器会取消内联
    //2. 内联函数必须放在调用它的方法的前面
    //3. C++编译器不一定会允许函数内联

    int a = 10;
    cout << "a: " << a << endl;
    //有循环不会内联
//    for(int i =0; i < 10000; i++){
//
//    }
    //if else switch 条件判断的语句过多,取消内联

}

// 当我们的函数本身代码执行的代价 大于函数调用压栈出栈的开销的时候,内联将没有任何意义
//g++ __attribute__((always_inline)) 属性 ,强制内联
int main01() {
    cout << "C++的函数" << endl;
    printA();
    //C语言中
    MYFUNC(1, 2);
    printB(); //把printB的代码嵌入到调用点
    int a = 11;
    cout << "a: " << a << endl;
    return 0;
}

/**
 * 方法实现
 */
void printA() {
    //定义实现
    cout << "printA" << endl;//C++编译器有可能会给你内联
}

4.2 默认参数和占位参数

4.2.1 默认参数

  1. 若你填写参数,使用你填写的,不用默认的
  2. 在默认参数规则中,如果默认参数出现,那么右边都必须有默认参数
  • 示例
//1.  若你填写参数,使用你填写的,不用默认的
void myPrint(int x = 1) {
    cout << "x: " << x << endl;
}

//2.  在默认参数规则中,如果默认参数出现,那么右边都必须有默认参数
//void myPrint2(int a,int b,int c = 1,int d= 2,int e) 不可以这么玩
void myPrint2(int a, int b, int c = 1, int d = 2) {
    cout << "a: " << a << " b: " << b << " c: " << c << " d: " << d << endl;
}

int main02_1() {
    cout << "函数的默认参数" << endl;

    myPrint(4);
    myPrint();
    cout << "============================="<< endl;

    myPrint2(1, 2, 3, 4);
    myPrint2(5, 6);//可只传部分连续的参数
    myPrint2(7, 8, 9);

    return 0;
}

  • 输出
函数的默认参数
x: 4
x: 1
=============================
a: 1 b: 2 c: 3 d: 4
a: 5 b: 6 c: 1 d: 2
a: 7 b: 8 c: 9 d: 2

4.2.2 占位参数

  • 占位参数 只有参数类型,没有参数名,但是调用函数时候占位参数也必须传值
/**
 * 函数占位参数
 * @param a
 * @param b
 * @return
 */
void myPrint3(int a, int b, int) {//占位参数 只有参数类型,没有参数名
    cout << "a: " << a << " b: " << b << endl;
}

int main02_2() {
    cout << "函数占位参数" << endl;

//    myPrint3(1,2);//报错 调不起来
    myPrint3(1, 2, 3);

    return 0;
}
  • 意义何在?
  1. 为以后的程序留下线索
  2. 兼容C语言中可能出现的不规范写法

4.3 函数重载

4.3.1 函数重载概念

  1. 函数名一致
  2. 参数不同(个数/类型)
void test(int a) {
    cout << "a: " << a << endl;
}

void test(char *a) {
    cout << "a: " << a << endl;
}

void test(int a, int b) {
    cout << "a: " << a << " b: " << b << endl;
}

//int test(int a,int b){
//    cout << "a: " <<a<< " b: " << b<< endl;
//    return 1;
//}

//重载
/*
 * 1. 函数名一致
 * 2. 参数不同(1,个数,类型)
 */
int main03_1() {
    cout << "函数重载" << endl;

    test(1);
    test("aaaaaa");
    test(1, 2);
    return 0;
}

4.3.2 函数重载的调用准则

4.3.3 函数重载遇上函数默认参数

  • 示例
void test1(int a, int b) {
    cout << "a: " << a << " b: " << b << endl;
}
/**
 * 该重载函数包含了一个默认参数
 * @param a
 * @param b
 * @param c
 */
void test1(int a, int b, int c = 3) {
    cout << "a: " << a << " b: " << b << endl;
}

void test1(int a) {
    cout << "a: " << a << endl;
}

int main03_2() {
    cout << "" << endl;
//    test1(1,2);//函数调用时,会产生二义性,无法调用
    return 0;
}

4.3.4 函数重载和函数指针结合

void myfunc5(int a) {
    cout << "a: " << a << endl;
}

void myfunc5(char *a) {
    cout << "a: " << a << endl;
}

void myfunc5(int a, int b) {
    cout << "a: " << a << " b: " << b << endl;
}

void myfunc5(char *a, char *b) {
    cout << "a: " << a << " b: " << b << endl;
}

//typedef声明一个函数类型:myTypeFunc5
typedef void (myTypeFunc5)(int a, int b);//声明了一个函数类型

//typedef声明一个函数指针类型
typedef void (*myPFunc5)(int a, int b);//一个函数指针只能指向一个具体的函数
//typedef void (*myPFunc5)(char* a,char* b);

typedef int SIZE_OF;

int main03_3() {
    cout << "函数指针与函数重载" << endl;
    SIZE_OF a;
    //初始化函数指针
    myTypeFunc5 *func = NULL;
    //将函数指针指向函数
    func = myfunc5;
    //通过函数指针调用函数
    func(1, 2);
    cout << "=============================" << endl;

    //函数指针初始化
    myPFunc5 func2 = nullptr;
    func2 = myfunc5;
    func2(1, 2);
//    func(1);
    cout << "=============================" << endl;

    char buf1[] = "aaaaa";
    char buf2[] = "adfwer";
//    func2(buf1, buf2);//这里不能调用,参数为指针说明调用的是myfunc5(char *a, char *b),必须定义一个该参数类型的函数指针

    return 0;
}

  • 输出
函数指针与函数重载
a: 1 b: 2
=============================
a: 1 b: 2
=============================

相关文章

网友评论

    本文标题:05 NDK-C++基础(一)

    本文链接:https://www.haomeiwen.com/subject/qylbbktx.html