Академический Документы
Профессиональный Документы
Культура Документы
For character and string processing in C language can be used different functions from standard
library header files ctype.h (character handling library for character processing), stdlib.h
(general utilities library for string conversion functions) and string.h (string handling library for
string processing). Descriptions and examples of using of these functions presented in the Help
option of Main menu of Turbo C++ compiler.
The most useful functions for string processing from header file string.h are:
strlen( ) to determine string length (returns number n of characters in the string str); Ex.:
n=strlen(str);
strcat( ) to join (concatenate) string s1 and string s2 together in one string s1. Ex.:
strcat(s1, s2);
strcpy( ) to copy (assign) contents of string s2 to string s1; Ex.: strcpy(s1, s2);
strcmp( ) to compare string s1 and string s2 (returns k=0 if strings are the same, k<0 if
string s1 < string
s2 (upper in dictionary) and k>0 if string s1 > string s2 (lower in dictionary); Ex.:
k=strcmp(s1, s2);
stricmp( ) this function is the same as strcmp( ) but not case sensitive; Ex.: k =
stricmp(s1, s2);
strrev( ) to reverse the characters in string str; Ex.: strrev(str);
strupr( ) to convert all characters in string str from lowercase to uppercase; Ex.:
strupr(str);
strlwr( ) to convert all characters in string str from uppercase to lowercase; Ex.:
strlwr(str);
Data types in C
1. Fundamental Data Types
o
Integer types
Floating Type
Character types
Arrays
Pointers
Structures
Enumeration
struct student
{char name[40];
int year;
int clas;
float average;
};
struct student st1, st2 , CL[26],*ps;
These define new data type names struct student and STUDENT and declare variables of structure type. The
purpose of typedef is to form complex types from more-basic machine types and assign simpler names to
such combinations.
Use of typedef with structs
typedef is a powerful tool that allows programmers to define and then use their own data
types. For example:
Note that in the typedef statement, the newly defined type name goes in the place where
a variable name would normally go.
There are a few benefits of using typedef with simple types such as the example above:
The typedef can be easily changed later, ( say to "typedef long int Integer;" ), which
would then affect all variables of the defined type.
However the real benefit of typedef comes into play with complex data structures, such as
structs:
11. Fields of struct data type. Access operation to the fields of structure.
Using typedef statement.
Fields in a structure can be referenced by following the name of the structure by a dot and the
name of the field.
France.population = 0;
You can also declare a pointer to type 'struct country' and use that to reference the fields in a
structure of that type. In this case, you follow the pointer name by an arrow and the name of the
field.
struct country Russia, *sptr;
sptr = &Russia;
sptr->population = 0;
In the example above, Russia is of type 'struct country; sptr is of type 'pointer to struct country';
and sptr->population is of type integer.
Accessing data fields within structs
The dot operator ( period ) is used to access named fields within struct variables:
john.nClasses = 4;
totalGPA += sue.gpa;
If two variables are of the same struct type, then they can be directly assigned one to the
other.
o
See the variable definitions above to see why some of these are invalid.
( Note: There is a potential problem if the structs contain pointers. See below for
details. )
joe = sue;
mary = carla;
alice = bill;
alice = charlie;
12. 1-D array of structures. Database in form of 1-D array of stuctures. Set of operations on
database.
Arrays of structures are possible, and are a good way of storing lists of data with regular fields, such as
databases. It is possible to define an array of structures for example if we are maintaining information of all the
students in the college and if 100 students are studying in the college. We can define an array S of structures as
shown in the following example.
typedef struct student
{ char name[40];
int year;
int clas;
float average;
} STUDENT;
STUDENT
S[100];
An array of structures in C language can be used for storing and processing information of the
same elements (structure objects) in a simple database. For creating a database using an array
of structures we have to determine a set of operations on it. Usually this set of operations on
an array of structures database can be represented as follows:
All these operations (options) can be implemented in a C language program for an array of
structures database processing by creating corresponding number of functions (subprograms)
and then by calling them from main( ) function in some order. Usually order and number of
these function calls (order and number of options needed to perform) depend on and are
determined by user during a working session with database. What is why it is necessary to
develop appropriate user interface for communication between user and program.
13. Infinit loop and switch statment for menu of operations on 1-D array of tructures
An array of structures in C language can be used for storing and processing information of the
same elements (structure objects) in a simple database. For creating a database using an array
of structures we have to determine a set of operations on it. Usually this set of operations on
an array of structures database can be represented as follows:
All these operations (options) can be implemented in a C language program for an array of
structures database processing by creating corresponding number of functions (subprograms)
and then by calling them from main( ) function in some order. Usually order and number of
these function calls (order and number of options needed to perform) depend on and are
determined by user during a working session with database. What is why it is necessary to
develop appropriate user interface for communication between user and program.
while( 1 ) // infinite while loop
{
clrscr( );
puts(\n \t \t menu:\n);
amount;
union
{
int
count;
char
name[4];
} udata;
char
discount;
} Transaction;
A union type definition contains the union keyword followed by an optional identifier (tag) and a
brace-enclosed list of members.
A union definition has the following form:
>>-union--+-identifier--------------------------+--------------><
|
.-----------.
'-+------------+--{----member--;-+--}-'
'-identifier-'
A union declaration has the same form as a union definition except that the declaration has no
brace-enclosed list of members.
The identifier is a tag given to the union specified by the member list. Once a tag is specified,
any subsequent declaration of the union (in the same scope) can be made by declaring the tag
and omitting the member list. If a tag is not specified, all variable definitions that refer to that
union must be placed within the statement that defines the data type.
The list of members provides the data type with a description of the objects that can be stored in
the union.
A union member definition has same form as a variable declaration.
15. Fields of union data type. Access and assignment operations for union data type.
Differenses between struct and union variables.
+-----+
union { int a; float b; } gives | a |
| b |
+-----+
struct { int a; float b; }
Structures are used where an "object" is composed of other objects, like a point object consisting of two
integers, those being the x and y coordinates:
typedef struct {
int x;
int y;
} tPoint;
Unions are typically used in situation where an object can be one of many things but only one at a time, such as
a type-less storage system:
typedef enum { STR, INT } tType;
typedef struct {
tType typ;
// typ is separate.
union {
int ival;
// ival and sval occupy same memory.
char *sval;
};
} tVal;
The C Preprocessor
Recall that preprocessing is the first step in the C program compilation stage -- this feature is
unique to C compilers.
The preprocessor more or less provides its own language which can be a very powerful tool to the programmer.
Recall that all preprocessor directives or commands begin with a #.
Use of the preprocessor is advantageous since it makes:
easier to read,
easier to modify
The preprocessor also lets us customise the language. For example to replace { ... } block statements delimiters
by PASCAL like begin ... end we can do:
#define begin {
#define end }
During compilation all occurrences of begin and end get replaced by corresponding { or } and so
the subsequent C compilation stage does not know any difference!!!.
Lets look at #define in more detail
#define
Use this to define constants or any macro substitution. Use as follows:
#define
For Example
#define FALSE 0
#define TRUE !FALSE
#include
This directive includes a file into code.
It has two possible forms:
#include <file>
or
#include "file"
<file> tells the compiler to look where system include files are held. Usually UNIX
systems store files in
usr include
directory.
"file" looks for a file in the current directory (where program was run from)
Included files usually contain C prototypes and declarations from header files and not
(algorithmic) C code (SEE next Chapter for reasons)
Macros with arguments must be defined using the #define directive before they can be used. The argument list
is enclosed in parentheses and must immediately follow the macro name. Spaces are not allowed between and
macro name and open parenthesis. For example:
#define MAX(x,y) ((x) > (y) ? (x) : (y))
Macro Caveats:
Macro definitions are not stored in the object file. They are only active for the duration of a single
source file starting when they are defined and ending when they are undefined (using #undef), redefined,
or when the end of the source file is found.
Macro definitions you wish to use in multiple source files may be defined in an include file which may
be included in each source file where the macros are required.
Today, #define is primarily used to handle compiler and platform differences. E.g., a define might hold a
constant which is the appropriate error code for a system call. The use of #define should thus be limited unless
absolutely necessary; typedef statements and constant variables can often perform the same functions more
safely.
Another feature of the #define command is that it can take arguments, making it rather useful as a pseudofunction creator. Consider the following code:
#define ABSOLUTE_VALUE( x ) ( ((x) < 0) ? -(x) : (x) )
...
int x = -1;
while( ABSOLUTE_VALUE( x ) ) {
...
}
It's generally a good idea to use extra parentheses when using complex macros. Notice that in the above
example, the variable "x" is always within its own set of parentheses. This way, it will be evaluated in whole,
before being compared to 0 or multiplied by -1. Also, the entire macro is surrounded by parentheses, to prevent
it from being contaminated by other code. If you're not careful, you run the risk of having the compiler
misinterpret your code.
Because of side-effects it is considered a very bad idea to use macro functions as described above.
int x = -10;
int y = ABSOLUTE_VALUE( x++ );
17. File pointer. Functions fopen() and fclose() for opening and closing a file.
Opening a file.
If we want to store data in a file in the external memory, we must use the following general
format for opening a file:
FILE *fp; fp=fopen(filename,mode); if(fp= =NULL){puts(file was not
oppend); return;}
The first statement declares the variable fp called file pointer to the structure data type FILE
that is defined in the stdio.h. The second statement opens the file named filename of
corresponding mode by using standard function fopen( ) and determines the value of file
pointer fp for the given file. This pointer, which points to the data stracture FILE that contains all
the information about the file, is used as a communication link between the operating system
and the program. The third statement verifies if function fopen( ) returned NULL pointer and if
so it means that file was not opened. The mode of file can be of three main kinds:
r- the file for reading data ; w- the file for writing data; a- the file for appending data.
Consider the following statements:
FILE *fp1, *fp2;
fp2=fopen(results.txt,w);
fp1=fopen(data.txt,r);
In these statements the fp1 and fp2 are created and assigned to open the files data and
results respectively. The file data is opened for reading and results is opened for writing. In
case the file results already exists, its contents are deleted and the file is opened as a new file.
If file data does not exist fp1 recieves
Closing a file.
The input output library stdio.h supports the function to close a file of the following format:
fclose(fp);
A file must be closed as soon as all operations on it have been completed. The function fclose( )
closes the file associated with the file pointer fp.
Observe the following part of program:
FILE *fp1 *fp2;
(results.txt,w);
fclose(fp1);
fp1=fopen (data.txt,r);
fp2=fopen
fclose(fp2);
The above program opens two files and closes them after all operations on them are completed.
Once a file is closed its file pointer can be reversed on other file.
The getc( ) and putc( ) functions are analogous to getchar( ) and putchar( ) functions and
handle one character at a time. The function call putc(ch, fp1 ); writes the character
contained in character variable ch to the file associated with the pointer fp1; similarly the
function getc( ) is used to read a character from a file that has been opened in read mode and
than to assign it to character variable ch = getc(fp2);
The fprintf( ) and fscanf( ) formatted functions for writing in and reading from file.
The fprintf( ) and fscanf( ) formatted functions are identical to printf( ) and scanf( )
formatted functions except that they work on files. The first argument of theses functions is a file
pointer fp which specifies the file to be used. The general form of fprintf( ) function call is:
fprintf(fp, control string, list);
Where fp is a file pointer associated with a file that has been opened for writing. The control string is file output
specifications, list may include variable, constant and string. For example:
fprintf(fp, %s %d %.2f\n, name, age, 7.5);
Here name is a character array, age is an integer variable and 7.5 is a float constant.
The general format of fscanf( ) function call is:
fscanf(fp, control string, list);
This statement would cause the reading of items of list conform the control string. For example:
fscanf(fp, %s%d, item, &quantity);
Once a file has been successfully opened, you can read from it using fscanf() or write to it using fprintf().
These functions work just like scanf() and printf(), except they require an extra first parameter, a FILE *
for the file to be read/written.
Note: There are other functions in stdio.h that can be used to read or write files. Look them up
in a good C reference.
Continuing our example from above, suppose the input file consists of lines with a username and an integer test
score, e.g.:
in.list
-----foo 70
bar 98
...
We might use the files we opened above by copying each username and score from the input file to the output
file. In the process, we'll increase each score by 10 points for the output file:
char username[9];
int score;
...
while (fscanf(ifp, "%s %d", username, &score) != EOF) {
fprintf(ofp, "%s %d\n", username, score+10);
}
...
The function fscanf(), like scanf(), normally returns the number of values it was able to read in. However,
when it hits the end of the file, it returns the special value EOF. So, testing the return value against EOF is one
way to stop the loop.
The bad thing about testing against EOF is that if the file is not in the right format (e.g., a letter is found when a
number is expected):
in.list
-----foo 70
bar 98
biz A+
...
then fscanf()
will not be able to read that line (since there is no integer to read) and it won't advance to the
next line in the file. For this error, fscanf() will not return EOF (it's not at the end of the file)....
Errors like that will at least mess up how the rest of the file is read. In some cases, they will cause an infinite
loop.
One solution is to test against the number of values we expect to be read by fscanf() each time. Since our
format is "%s %d", we expect it to read in 2 values, so our condition could be:
while (fscanf(ifp, "%s %d", username, &score) == 2) {
...
Now, if we get 2 values, the loop continues. If we don't get 2 values, either because we are at the end of the file
or some other problem occurred (e.g., it sees a letter when it is trying to read in a number with %d), then the loop
will end.
Another way to test for end of file is with the library function feof(). It just takes a file pointer and returns a
true/false value based on whether we are at the end of the file.
To use it in the above example, you would do:
while (!feof(ifp)) {
if (fscanf(ifp, "%s %d", username, &score) != 2)
break;
fprintf(ofp, "%s %d", username, score+10);
}
Note that, like testing != EOF, it might cause an infinite loop if
Note: When you use fscanf(...) != EOF or feof(...), they will not detect the end of the file
until they try to read past it. In other words, they won't report end-of-file on the last valid read,
only on the one after it.
Functions
File access
fopen
opens a file
(function)
freopen
fclose
closes a file
(function)
fflush
fwide
setbuf
(function)
setvbuf
Direct input/output
fread
fwrite
writes to a file
(function)
Unformatted input/output
Narrow character
fgetcgetc
fgets
fputcputc
fputs
getchar
gets
(until C++14)
putchar
puts
ungetc
Wide character
fgetwcgetwc
fgetws
fputwcputwc
fputws
getwchar
putwchar
ungetwc
Formatted input/output
Narrow/multibyte character
scanffscanfsscanf
vscanfvfscanfvsscanf
(C++11)(C++11)(C++11)
printffprintfsprintfsnprintf
(C++11)
wscanffwscanfswscanf
vwscanfvfwscanfvswscanf
(C++11)(C++11)(C++11)
wprintffwprintfswprintf
vwprintfvfwprintfvswprintf
File positioning
ftell
fgetpos
fseek
fsetpos
rewind
Error handling
clearerr
clears errors
(function)
feof
ferror
perror
Operations on files
remove
erases a file
(function)
rename
renames a file
(function)
tmpfile
tmpnam
Types
Defined in header <cstdio>
Type Definition
FILE
fpos_t
non-array type, capable of uniquely specifying a position in a file, including its multibyte
parse state
Bitwise operators are special types of operators that are used in programming the processor. In processor,
mathematical operations like: addition, subtraction, addition and division are done using the bitwise operators
which makes processing faster and saves power.
The Bitwise operators supported by C language are listed in the following table. Assume variable A holds 60
and variable B holds 13, then:
Operator
Description
Example
Binary AND Operator copies a bit to the result if it exists in (A & B) will give 12 which is 0000
&
both operands.
1100
(A | B) will give 61 which is 0011
|
Binary OR Operator copies a bit if it exists in either operand.
1101
Binary XOR Operator copies the bit if it is set in one
(A ^ B) will give 49 which is 0011
^
operand but not both.
0001
(~A ) will give -61 which is 1100 0011
Binary Ones Complement Operator is unary and has the
~
in 2's complement form due to a
effect of 'flipping' bits.
signed binary number.
Binary Left Shift Operator. The left operands value is moved A << 2 will give 240 which is 1111
<<
left by the number of bits specified by the right operand.
0000
Binary Right Shift Operator. The left operands value is
A >> 2 will give 15 which is 0000
>>
moved right by the number of bits specified by the right
1111
operand.
20. Operators for bitwise logical operations
Four of the bitwise operators have equivalent logical operators. They are equivalent in that they have the same
truth tables. However, logical operators treat each operand as having only one value, either true or false, rather
than treating each bit of an operand as an independent value. Logical operators consider zero false and any
nonzero value true. Another difference is that logical operators perform short-circuit evaluation.
The table below matches equivalent operators and shows a and b as operands of the operators.
Bitwise
Logical
a & b
a && b
a | b
a || b
a ^ b
a != b
~a
!a
has the same truth table as ^ but unlike the true logical operators, by itself != is not strictly speaking a logical
operator. This is because a logical operator must treat any nonzero value the same. To be used as a logical
operator != requires that operands be normalized first. A logical not applied to both operands wont change the
truth table that results but will ensure all nonzero values are converted to the same value before comparison.
This works because ! on a zero always results in a one and ! on any nonzero value always results in a zero.
!=
The example below shows that the truth tables for these Bitwise and Logical operators are identical but also
demonstrates how they act on their operands differently. The need to normalize operands for != can be
demonstrated by introducing a char T2 = 0x02 and packing it in an array. Mixing T2 with T will show them
being treated the same unless the operand NOTs around != are removed.
b1
00011010
b2
01101000 104
26
Bits shifted out of the MSB are lost, bits shifted in through the LSB are always zero.
or
x >> n
/* 1011 0101 */
It is usually preferred to use unsigned types for bitwise operations to avoid non-portable behaviour.
Note: right shift is equivalent to division by 2^n if operand is non-negative or machine uses arithmetic
right-shift.
Passing Arguments to
main()
is passed two arguments from the shell: an integer and a pointer to an array of strings. Traditionally
these are declared as follows:
main()
Here argc (``argument count'') contains one plus the number of arguments passed to the program from the
command line and argv (``argument vector'') contains a series of char pointers to these arguments. The first
element in argv is always the name of the program itself, so argc is always at least 1. The library function
getopt() can perform simple parsing of command-line arguments; see the listing in section 3c of the man
pages. Here's a more simple example of passing two numbers and a string. Note the error trapping to force the
user to conform to the expected usage:
#include <stdio.h>
#include <stdlib.h> /* for atoi() */
int main(int argc,char *argv[]) {
int m,n;
if (argc != 4) {
printf("Usage: %s m n filename\n",argv[0]);
return 1;
}
m = atoi(argv[1]); /* convert strings to integers */
n = atoi(argv[2]);
printf("%s received m=%i n=%i filename=%s\n",argv[0],m,n,argv[3]);
return 0;
}
int
*A, n;
scanf(%d, &n);
A = (int*) malloc( n* sizeof(int) ); // or
if (A == NULL)
// or shorter
if ( !A)
{
puts( \n Memory was not allocated); // or using here return statement
}
After this code it is possible to use allocated memory having access to it by means of pointer A
which
can be used as name of dynamic array.
There are 2 kinds of memory allocation in C/C++ languages: a) static memory allocation and b)
dynamic memory allocation.
Static memory allocation refers to the process of allocating memory for the named variables at
compile time before the corresponding program is executed. Static memory allocation is
performed by compiler conform variable declaration statements for global and local variables.
Global variables, constants (specified by keyword const) and static memory class local variables
(specified by keyword static) having lifetime of a whole program are allocated in fixed memory,
but automatic memory class local variables (specified by keyword auto or by default) having
lifetime of a function call are allocated on the stack (is a part of computer memory where data is
added and removed in a Last-In-First-Out (LIFO) manner). Memory allocated statically can not be
reallocated or freed (deallocated) by program itself (by programmer).
Dynamic memory allocation is the allocation memory for the unnamed dynamic variables at
runtime,
during program execution. In C language dynamic memory allocation is performed by program
itself (by programmer) using special functions of standard library header file stdlib.h . Dynamic
memory is allocated from a large part of unused memory area called the heap (also called the
free store ). Dynamic memory is allocated, accessed, managed, reallocated and freed at runtime
by using pointer variables just allocated statically before at compile time.
Functions malloc( ) and free( ).
The function malloc( ) is the basic function used to allocate memory on the heap (on the free
store) in C language. Its prototype is:
void* malloc(int size);
where void* is the type of function which represents void pointer type of returning value and
size is the parameter variable name which represents the size of the block of memory needed in
bytes.
If the allocation is performed successfully, function malloc( ) returns the beginning address (of
void pointer type) of the block of memory allocated. Note that because malloc( ) function returns
a void pointer it is recommended to cast the type of returning value of function malloc( ) to a
needed pointer type especially for compatibility of C language programs with C++ language
programs.
If the allocation is not performed successfully function malloc( ) returns the NULL pointer value
to indicate that no memory was allocated and usually in this case the program execution is
terminated.
Memory allocated by malloc( ) function will continue to exist until the program terminates or
the memory explicitly deallocated by the programmer ( that is, the block is said to be freed).
This is achieved by use of the function free( ). Its prototype is:
void free ( void* p);
where p is the void pointer parameter for the beginning address of the block of memory just
allocated before by malloc( ) function or other functions used for dynamic memory allocation.
After free( ) function call, the memory pointed to by p is not more available for the program and
what is why, it is strictly recommended to assign NULL pointer value to the pointer p immediately
after free( ) function call.
In C/C++ languages the name (identifier) of an array is equivalent to the address of its first
element ,so in fact they are the same concept. For example, supposing these two declarations:
int A[20]; int * p;
The following assignment operations would be valid and equivalent: a) p=A; b) p=&A[0];
After that, p and A would be the same and would have the same properties so, that name of
pointer p can be used as another name of array A. The only difference is that we could change
the value of pointer p by another one, whereas A will always point to the first of the 20 elements
of type int with which it was defined. Therefore, unlike p, which is an ordinary pointer, A is an
array, and an array can be considered a constant pointer.
Therefore, the following assignments would not be valid: A=p; A=A+1;
Because A is an array, so it operates as a constant pointer, and we cannot assign values to
constants.
Concerning 1-D arrays we used square brackets [ ] in order to specify the index (subscript) of
an element of the 1-D array to which we wanted to refer. Well, this square brackets operator [ ]
or the indexation operator is also a dereferencing operator known as offset operator. It
dereferences the variable (name of array or pointer) it follows just as the dereferencing pointer
operator * does after adding the subscript to the name of array and thus obtaining the memory
address of the corresponding element.
So, the expression A[5] is equivalent to the expression * (A+5) both represents the value
of the 6-th element of int type counted from the starting address A. Analogically, expressions
&A[5] and (A+5) are equivalent and both represent the address of the memory where this
element is stored.
Pointers to Pointers
C allows the use of pointers that point to pointers, that these, in its turn, point to
data (or even to other pointers). In order to do that, we only need to add an asterisk
(*) for each level of reference in their declarations:
char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;
This, supposing the randomly chosen memory locations for each variable of 7230,
8092 and 10502, could be represented as:
The value of each variable is written inside each cell; under the cells are their
respective addresses in memory.
The new thing in this example is variable c, which can be used in three different levels
of indirection, each one of them would correspond to a different value:
- c has type char** and
a value of 8092
- *c has type char* and
a value of 7230
- **c has type char and
a value of 'z'
A NULL pointer is a regular pointer of any pointer type which has a special value that
indicates that it is not pointing to any valid reference or memory address.
int * p;
p = NULL;// p has a NULL pointer value
Do not confuse NULL pointers with void pointers. A null pointer is a value that any
pointer may take to represent that it is pointing to "nowhere", while a void pointer is
a special type of pointer that can point to somewhere without a specific type.
27.
We've seen that it's straightforward to call malloc to allocate a block of memory which can simulate an array,
but with a size which we get to pick at run-time. Can we do the same sort of thing to simulate multidimensional
arrays? We can, but we'll end up using pointers to pointers.
If we don't know how many columns the array will have, we'll clearly allocate memory for each row (as many
columns wide as we like) by calling malloc, and each row will therefore be represented by a pointer. How will
we keep track of those pointers? There are, after all, many of them, one for each row. So we want to simulate an
array of pointers, but we don't know how many rows there will be, either, so we'll have to simulate that array (of
pointers) with another pointer, and this will be a pointer to a pointer.
This is best illustrated with an example:
#include <stdlib.h>
int **array;
array = malloc(nrows * sizeof(int *));
if(array == NULL)
{
fprintf(stderr, "out of memory\n");
exit or return
}
for(i = 0; i < nrows; i++)
{
array[i] = malloc(ncolumns * sizeof(int));
if(array[i] == NULL)
{
fprintf(stderr, "out of memory\n");
exit or return
}
}
is a pointer-to-pointer-to-int: at the first level, it points to a block of pointers, one for each row. That
first-level pointer is the first one we allocate; it has nrows elements, with each element big enough to hold a
pointer-to-int, or int *. If we successfully allocate it, we then fill in the pointers (all nrows of them) with a
pointer (also obtained from malloc) to ncolumns number of ints, the storage for that row of the array. If this
isn't quite making sense, a picture should make everything clear:
array
Once we've done this, we can (just as for the one-dimensional case) use array-like syntax to access our
simulated multidimensional array. If we write
array[i][j]
we're asking for the i'th
pointer pointed to by array, and then for the j'th int pointed to by that inner pointer.
(This is a pretty nice result: although some completely different machinery, involving two levels of pointer
dereferencing, is going on behind the scenes, the simulated, dynamically-allocated two-dimensional ``array'' can
still be accessed just as if it were an array of arrays, i.e. with the same pair of bracketed subscripts.)
If a program uses simulated, dynamically allocated multidimensional arrays, it becomes possible to write
``heterogeneous'' functions which don't have to know (at compile time) how big the ``arrays'' are. In other
words, one function can operate on ``arrays'' of various sizes and shapes. The function will look something like
func2(int **array, int nrows, int ncolumns)
{
}
function does accept a pointer-to-pointer-to-int, on the assumption
This
that we'll only be calling it with
simulated, dynamically allocated multidimensional arrays. (We must not call this function on arrays like the
``true'' multidimensional array a2 of the previous sections). The function also accepts the dimensions of the
arrays as parameters, so that it will know how many ``rows'' and ``columns'' there are, so that it can iterate over
them correctly. Here is a function which zeros out a pointer-to-pointer, two-dimensional ``array'':
void zeroit(int **array, int nrows, int ncolumns)
{
int i, j;
for(i = 0; i < nrows; i++)
{
for(j = 0; j < ncolumns; j++)
array[i][j] = 0;
}
}
Finally, when it comes time to free one of these dynamically allocated multidimensional ``arrays,'' we must
remember to free each of the chunks of memory that we've allocated. (Just freeing the top-level pointer, array,
wouldn't cut it; if we did, all the second-level pointers would be lost but not freed, and would waste memory.)
Here's what the code might look like:
for(i = 0; i < nrows; i++)
free(array[i]);
free(array);
28. As we defined before (see lecture 2) a composed or structured variable represents named
location in the
memory of computer, where more than one value can be stored. There are
different kinds of composed variables which are also called data structures. One of these kinds is
an array.
In programming, an array is a composed variable or a data structure, which represents
named set of values ( also called elements or components) of the same type, located in the
memory of computer
continuously one after one. Arrays are very often used in programming for storing and
processing data.
In mathematics arrays are known as matrices and are used for solving different problems.
Usually in computer programming are used one-dimensional (1-D) and two-dimensional (bedimensional) arrays (2-D). Dimension of an array depends on the number of integer values called
indices (subscripts) used for determining the position of a given element and for accessing to the
corresponding element. One subscript is used for indicating elements of 1-D array (vector) and
two subscripts are used for elements of 2-D array(matrix). In mathematics 1-D arrays also called
vectors represent special kind of
matrices (2-D arrays) having only one line (row) or one column.
In C/C++ languages indices (subscripts) are enclosed in square brackets [ ] represented the
indexation
operator. In C/C++ languages values of subscripts start by 0.
char C[10][30];
Arrays can be initialized during declaration in such a way:
int A[5]={3,-5,6,7, 0};
float B[2][3]={ {3.2, 2.5, 0},{-5.3, 9, 47.55 } };
or float B[2][3]={ 3.2, 2.5, 0, -5.3, 9, 47.55 };
The function factorial() gets called from main() with number having the value 4 as the
argument.
Within the factorial() function itself, because the argument is greater than 1, the
statement executed is: return a*Factorial(a-1);
This is the second return statement in the function, and it calls factorial() again with the
argument value 3 from within the arithmetic expression. This expression cant be
evaluated, and the return cant be completed until the value is returned from this call to
the function factorial() with the argument 3.
This continues until the argument in the last call of the factorial() function is 1. In this
case, the first return statement return a; is executed and the value 1 is returned to the
previous call point. This call point is, in fact, inside the second return in the factorial()
function, which can now calculate 2 * 1 and return to the previous call.
In this way, the whole process unwinds, ending up with the value required being returned
to main().