C++(一)

1. i++与++i 问题

i++: i先参与运算,再自增1;
++i: i先自增加1, 再参与运算。


2. 三元运算符

条件?操作一:操作二
条件为真,执行操作一,条件为假,执行操作二。

注意确定“条件”的范围,有时候并不是把全部的左边表达式作为“条件”的
例如:

int  a=9,c;
c=(a++==9);

这里c等于括号里的判定结果,a++==a 为真,所以 c = 1 。

int  a=9,c;
c=(a++==9)?a++:a--;

这里三元运算的条件是(a++=9),而不是问号左边整个的表达式(c=(a++==9))。
条件“a++==9” 为真,所以 c=a++; a在前边的判断条件中做了+1操作了,所以 c=10++,即c=10。


3. printf 输出中含相关计算问题

printf计算参数时是从右到左栈的。即先计算最右侧的结果,再依次向左侧计算,这样右侧的计算结果可能会影响到左侧的计算。
例如:

#include <stdio.h>

int main()
{
    int a=9;
    printf("%d, %d\n,",a,a++);
    system("pause");
    return 0;
}
输出是 10,9 ; 而不是 9, 9


4. switch语句

switch语句允许测试一个变量等于多个值时的情况,每一个值称为一个case,case必须是一个整形或枚举类型。
switch重点需要关注分支语句中break的使用,如果当前分支满足条件且没有break,则当前分支之后所有的case语句都将会执行。
例如不加break:

     int a = 2;
    switch (2)
    {
    default:
        printf("default\n");
        break;
    case 2:
        printf("%d\n",2);
        //break;
    case 3:
        printf("%d\n",3);
        break;    
    }

则输入为 2 3。

default语句可以写到首个判断位置上,所有case都不满足也会执行default,但是若满足(不论加不加break)则不会执行位于前边的default。


5. const

用const修饰的数据的值是不可以被修改的。格式: const type name=value; 或 type const name=value; (通常需要在声明的同时初始化(赋值))。

const常量的特点:

  • 1. 便于进行类型检查,const定义时候变量的类型也已经限定了。
  • 2. 防止魔鬼数字的出现,便于修改调整代码。
  • 3. 保护不想被改变的变量被意外修改,增强程序的健壮性。
  • 4. const定义常量可以节省空间,避免重复分配内存。const只是给出对应的内存地址,只有一份拷贝,不像#define宏那样给出的是立即数,有多份拷贝。
  • 5. const的使命就是用来优化(替代)预编译宏定义的,在C++中只使用const常量而不使用宏常量。
  • const修饰指针

分析const修饰的到底是指针本身还是指针指向的内容,方法是:先忽略类型名,看const离谁近,就是修饰的谁。如:

int arr[5];
const int *p = arr; //const 修饰*p,p是指针本身,可以变, *p是指针指向的实际对象,不可变。

 

const修饰类中成员函数
对于不修改类成员变量的成员函数,可以认为这些成员函数是“只读”函数,最好把这些函数加上const关键字进行标识,提高程序的可读性和可靠性。例如:

class School{
    string name;
    public:
    string GetName() const;
};
string School::GetName() const { return name; }


注意在声明和实现函数的时候都需要加上const关键字修饰。


const、volatile和mutable


const修饰常量,值不可修改。
volatile:volatile修饰表示该变量可以被某些编译器未知的因素(如硬件寄存器的更改)更改,所以编译器不再对该变量的访问做优化,每次总是从它所在的内存地址读取数据。volatile可以用来保证对特殊地址的稳定访问。
mutable:mutable的意思是“易变的,可变的”,C++中mutable的设置就是为了在类中突破const的限制的。使用mutable修饰的类的成员变量,也可以被以const修饰的成员函数修改。


sizeof

操作符(运算符)和函数区别
操作符的作用是在表达式中用于连接不同对象的运算符。操作符需要编译器进一步的解释。并且操作符不能覆盖,不可以自定义,比如你不可以通过自定义使运算符“+”的意义变成“*”;

运算符是语言本身的特性,有固定的语义,并且编译器也十分清楚运算符操作意味着什么操作,由编译器负责解释语义,生成相应的代码。

函数是定义的一系列操作,任何函数都可以重载或覆盖。

库函数是依赖于库的,由库具体实现,在一定程度上是独立于语言本身的。理论上,编译器不知道也不关心函数具体实现的功能和函数里边的操作,编译器只负责编译函数,以及调用该函数时参数和返回值符合语法,并生成相应 call 函数的代码。

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。

sizeof是一种单目操作符,注意并不是一个函数。 是操作符也意味着“sizeof()”中的括号不是必须的, sizeof(x) 也完全可以写作 sizeof x 。
sizeof以字节形式给出其操作数的存储大小。操作数也可以是一个表达式,这时给出的是表达式结果的字节大小。但是一定注意,sizeof中的表达式并不会改变已有变量的值。例如:

    int i = 1;
    char s = 1;
    
    printf("%d",sizeof i);
    printf("%d",sizeof(s = s+i));
    printf("%d",s);

s的值仍然是1,而不是2。

32位系统中,sizeof(int)是4字节。sizeof(指针)返回指针的大小,也是4 。sizeof(short)是2 。sizeof(long)是4。sizeof(double)是8字节。 C++中sizeof(bool)是1, 一个空类A的sizeof(A)是1。 C中没有bool类型。sizeof(数组)返回的是数组中所有元素的总大小。数组名作为函数的入参时,会自动转换成指针类型,这时sizeof(入参数组)=4 。

    int i = 1;
    short ii =4;
    double i_double;

    char s1[] = "12345";
    char s2[10] = "12345";
    char *s3 = "12345";
    char s4[] = {'1','2','3','4','5'};
    char s5[]="12345\n";

    struct
    {
        short a;
        long b;
    }A;

    printf("%d\n",sizeof(i));  //int类型, 所以是4
    printf("%d\n",sizeof(ii));  //short类型, 所以是2
    printf("%d\n",sizeof(i_double));  //double类型, 所以是8
    printf("%d\n",sizeof(s1)); //字符串初始化的字符数组,以'\n'结尾,所以是6
    printf("%d\n",sizeof(s2)); //字符数组,预分配大小10, 所以是10
    printf("%d\n",sizeof(s3)); //指针本身的size,所以是4
    printf("%d\n",sizeof(*s3)); //字符数组中第一个字符的size,所以是1
    printf("%d\n",sizeof(s4)); //字符数组,一共5个,不存在额外的结尾,所以是5
    printf("%d\n",sizeof(s5)); //'\n'算字符数组中的一位,所以是7
    printf("%d\n",sizeof(A)); //按结构中最大的字节单位对齐,相当于2个long,所以是8

 

C中的字符数组和字符串

C中没有类似C++中的string类来处理字符串,C中对字符串的处理需要转换成字符数组处理。

有两种方式给C中字符数组赋值:
char array1[] = {'a','b','c'};
char array2[] = "abc";
sizeof(array1)=3, 而sizeof(array2)=4, 因为使用字符串初始化的字符数组会在末尾加上一个'\n'符号表示结尾。


与类结构相关的sizeof

class AA
{
    public:
        int a;
        static int b;
        char c;
        float d;
    
        AA();
        ~AA();
    
    private:
        float e;    
};

class BB
{
    public:
        int a;
        static int b;
        char c;

        BB();
        ~BB();
    
    private:
        float e;    
        double d;
};

sizeof(AA) = 16
sizeof(BB) = 24

说明:
1. 类中的静态变量是该类的所有对象所共享的,存放在全局数据区,sizeof计算的是栈中分配的大小,所以不会计算静态变量所占空间;
2. 编译器会对struct、类这种结构做优化,也就是数据对齐。 对齐的原则是按照占空间最大的变量为基准,并且有“合并”机制,即两个或多个合占一个基准空间。

sizeof与strlen区别

sizeof: 变量占用空间大小,是一个操作符; 可以求整型或字符型数组(或指针)
strlen:字符串的长度,是一个函数。参数必须是字符型指针 char*,长度不包含最后的NULL字符。只能求字符型数组(或指针)。strlen函数返回类型是一个无符号整型!!

 

strlen陷阱

1. char str[5] = "12345"; strlen(str) = ?

这种情况下,字符的个数等于字符数组的大小,strlen(str)的大小是不确定的,由于str的最后一位不是NULL,所以strlen函数会继续往后检索,直到遇到‘\0’,认为是结尾,所以大小不确定。

2. char str[10] = "12345"; strlen(str) = ?

strlen函数求的是字符串的实际长度,而不管分配的空间大小,所以strle(str) = 5 。

3. if(strlen(a) >= strlen(b)){} 与 if(strlen(a) - strlen(b) >= 0){} 是否等价?

strlen()函数的返回值是无符号整型,所以 strlen(a) - strlen(b) 结果也是一个无符号整型的,导致 strlen(a) - strlen(b) >= 0 条件是永远成立的。还有类似的如 strlen(a) - 5 >= 0 这种也是永远成立(5被当成了无符号类型)。 第一种写法是合适的。

 

内联函数

内联函数不需要中断调用,在编译的时候内联函数的代码会被直接镶嵌到目标代码中,可以加快程序运行速度,但是内存占用比普通程序要高,增加了代码量。可以考虑使用内联函数的场合: 1. 一个函数(功能)不断被重复调用; 2. 函数只有简单的几行,不含 for、while、switch等语句。
inline 关键字必须根函数定义体(不是函数声明)放到一起才能使函数称为内联函数。
inline 关键字只是给编译器一个建议,建议将inline修饰的函数定义为内联函数,并不能强制,所以编译器不一定会接受内联建议。
内联函数相比宏定义的优势是可以做类型检查。

 

指针

指针首先是一个变量,它的值是一个内存中的地址(逻辑地址),该内存地址上存放有数据。所以指针就是一个地址变量!!同样该地址变量也分为不同的类型,如 int*、char*、double*等,地址变量的类型决定了系统如何对待该地址上保存的数据。


为什么 int* p; *p=5; 不可行?
如果定义指针的时候没有初始化,也就意味着并没有给指针p分配一个合法的地址(大多数系统会随机给一个地址),所以直接在一个不存在的地址上(非法的地址上)存放5,就会导致错误。

NULL指针(空指针)
NULL指针就是不指向任何指针的指针,或者可以理解为NULL指针所指向的内存地址上什么都没有。 int* p = NULL; 和 int* p = 0; 都可以定义一个空指针。


指向指针的指针
int a = 0; int* p = &a; int** pp = &p;  则pp是指向指针的指针。即pp代表一个内存地址,这个内存地址上存放的数据也是一个指针,这个指针又指向它实际的数据地址。
**pp = *(*pp) (右结合特性)

int *p; //P是一个返回整型数据的指针
int p[3]; //P 是一个由整型数据组成的数组
int *p[3]; //P 是一个由返回整型数据的指针所组成的数组 (指针数组,数组里边的元素都是指针)
int (*p)[3]; //P 是一个指向由整型数据组成的数组的指针  (数组指针, 指针指向的是一个数组)
int (*p)(int); //P 是一个指向有一个整型参数且返回类型为整型的函数的指针

 

引用
在C++中与指针相对的,还有一个引用的概念。引用就是对一个变量起一个“别名”,任何对变量引用的操作都会影响到变量本身。
引用与取地址符号的区别: int &b = a;  c = &a; 前者是引用,后者是取地址,在数据类型后跟一个&,就是引用。
引用一般用在函数传参时,在函数内部对引用的改变会同时改变原变量。

 

指针和引用区别
1. 非空区别: 一个引用不可以指向空值,指针可以指向空值。
2. 合法性区别: 引用在使用之前不需要测试它的合法性(只要存在,一定合法),指针在使用前,总是应该先判断指针是否为空。
3. 可修改区别: 引用指向一个对象之后不可修改,指针指向一个对象之后还可以修改指针指向别的对象。
4. 初始化区别: 指针声明时候可以不初始化,引用必须在声明同时初始化。

通过指针交换两个数


void swap1(int* a, int* b)
{
    int* temp;
    temp = a;
    a = b;
    b = temp;
}
void swap2(int* a, int* b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

swap1函数实现不了两个数据交换的目的,它函数里边相当于让指针a、b互换指向,但是这种指向关系在函数结束之后就失效了。
swap2函数可以实现两个数据交换的目的。在函数内部直接更改了指针a、b指向的内存上的数据。这种改变不会随着函数结束而失效。


malloc、free
malloc是在C语言中用于在程序运行时在堆中进行动态内存分配的库函数。free是进行内存释放的库函数。
malloc的函数原型: (void *)malloc(int size)
malloc函数的返回值是一个void类型的指针,参数为int类型数据,即申请分配的内存大小,单位是字节(byte,8位bit)。
内存分配成功之后,malloc函数返回这块内存的首地址。
需要一个指针来接收malloc函数返回的地址,但是由于函数的返回值是void *类型的,所以必须强制转换成所接收的类型。也就是说,这块内存将要用来存储什么类型的数据:
char *p = (char *)malloc(100); //在堆上分配100个字节的空间,并把这块空间的首地址强制转换成 char* ,再赋值给指针变量p。
释放使用free: free(p); 斩断指针变量与这块内存的指向关系。 使用free释放之后,需要使用 p=NULL; 把p置为空指针,防止p成为野指针。

 

内存中用户存储空间的分配

程序区: 存放程序语句;
静态存储区: 存放全局变量,局部静态变量,常量区(常量字符串)就属于静态存储区; 静态存储区在程序执行的整个生命周期都存在,直到程序执行完毕。 所谓静态变量就是使用static修饰的变量。
动态存储区: 函数形参、局部动态变量、函数调用线程保护和返回地址,动态分配和回收,一般生命周期只限于当前调用的函数中。

 

C中字符指针和字符数组的恩恩怨怨

char* p = "Hello world";
char s[] = "Hello world";

使用字符串常量定义的指针字符串位于静态存储区的常量区这个区域的内存值不允许被程序修改(所以类似 p[0]='x'的操作是错误的),并且在程序的整个生命周期都存在;


而字符数组定义的字符串位于栈中,由系统自动分配和回收,可以被程序修改(可以使用s[0]='x'修改)。生命周期一般较短。
基于字符指针和字符数组两者在内存中的位置和生命周期不同,可以推知,在局部函数中可以使用字符指针返回字符串,而字符数组不可以在局部函数中返回字符串。


但是使用static修饰的字符数组就变成了一个静态变量,分配位置位于静态存储区,在整个程序生命周期都存在,在局部函数中返回该字符串变量也是可以的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值